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
## Version 5.2
* Updated PHP requirement to 8
## Version 5.1
* 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.

8
Jenkinsfile vendored
View File

@ -34,6 +34,13 @@ pipeline {
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') {
agent any
steps {
@ -43,6 +50,7 @@ pipeline {
cloverReportDir: '',
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
[![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)]
@ -31,7 +31,7 @@ Update your anime/manga list on Kitsu.io and Anilist
### Requirements
* PHP 7.4+
* PHP 8
* PDO SQLite or PDO PostgreSQL (For collection tab)
* GD extension for caching images

View File

@ -17,7 +17,9 @@
<directory>../tests/Ion</directory>
</testsuite>
</testsuites>
<logging/>
<logging>
<junit outputFile="logs/junit.xml"/>
</logging>
<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_HOST" value="localhost"/>

View File

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

View File

@ -2,17 +2,19 @@ parameters:
checkGenericClassInNonGenericObjectType: false
checkMissingIterableValueType: false
inferPrivatePropertyTypeFromConstructor: true
level: 7
autoload_files:
- %rootDir%/../../../tests/mocks.php
level: 8
paths:
- src
- ./console
- index.php
ignoreErrors:
- '#Access to an undefined property Aviat\\\Ion\\\Friend::\$[a-zA-Z0-9_]+#'
- '#Call to an undefined method Aviat\\\Ion\\\Friend::[a-zA-Z0-9_]+\(\)#'
- "#Offset 'fields' does not exist on array#"
- '#Function imagepalletetotruecolor not found#'
- '#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:
- 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
*
* @param FormBody|string $body
* @throws \TypeError
* @return self
*/
public function setBody($body): self
public function setBody(FormBody|string $body): self
{
$this->request->setBody($body);
return $this;
@ -129,7 +128,6 @@ abstract class APIRequestBuilder {
* Set body as form fields
*
* @param array $fields Mapping of field names to values
* @throws \TypeError
* @return self
*/
public function setFormFields(array $fields): self
@ -293,74 +291,6 @@ abstract class APIRequestBuilder {
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
*

View File

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

View File

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

View File

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

View File

@ -58,10 +58,9 @@ trait CacheTrait {
* @param string $key
* @param callable $primer
* @param array|null $primeArgs
* @return mixed|null
* @throws InvalidArgumentException
* @return mixed
*/
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);

View File

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

View File

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

View File

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

View File

@ -26,7 +26,6 @@ use Amp\Http\Client\Request;
use Amp\Http\Client\Response;
use Aviat\AnimeClient\Kitsu as K;
use Aviat\AnimeClient\API\APIRequestBuilder;
use Aviat\AnimeClient\API\FailedResponseException;
use Aviat\AnimeClient\Enum\EventType;
use Aviat\Ion\Di\ContainerAware;
use Aviat\Ion\Di\ContainerInterface;
@ -157,7 +156,6 @@ final class RequestBuilder extends APIRequestBuilder {
* @param string $name
* @param array $variables
* @return array
* @throws Throwable
*/
public function mutate(string $name, array $variables = []): array
{
@ -181,7 +179,6 @@ final class RequestBuilder extends APIRequestBuilder {
* @param string $url
* @param array $options
* @return Response
* @throws \Throwable
*/
public function getResponse(string $type, string $url, array $options = []): Response
{
@ -200,15 +197,79 @@ final class RequestBuilder extends APIRequestBuilder {
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
*
* @param string $type
* @param string $url
* @param array $options
* @throws JsonException
* @throws FailedResponseException
* @throws Throwable
* @return 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
* logical and workable structure
*
* @param array $item API library item
* @param array|object $item API library item
* @return AnimeListItem
*/
public function transform($item): AnimeListItem
public function transform(array|object $item): AnimeListItem
{
$item = (array)$item;
$animeId = $item['media']['id'];
$anime = $item['media'];
@ -115,14 +116,13 @@ final class AnimeListTransformer extends AbstractTransformer {
* @param array $item Transformed 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']);
$rewatching = (array_key_exists('rewatching', $item) && $item['rewatching']);
$untransformed = FormItem::from([
'id' => $item['id'],
'anilist_item_id' => $item['anilist_item_id'] ?? NULL,
'mal_id' => $item['mal_id'] ?? NULL,
'data' => [
'status' => $item['watching_status'],

View File

@ -29,11 +29,12 @@ final class AnimeTransformer extends AbstractTransformer {
* Convert raw api response to a more
* logical and workable structure
*
* @param array $item API library item
* @param array|object $item API library item
* @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'];
$characters = [];
$links = [];

View File

@ -28,12 +28,13 @@ use Locale;
final class CharacterTransformer extends AbstractTransformer {
/**
* @param array $characterData
* @param array|object $item
* @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 = [];
$media = [
'anime' => [],

View File

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

View File

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

View File

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

View File

@ -29,11 +29,12 @@ final class MangaTransformer extends AbstractTransformer {
* Convert raw api response to a more
* logical and workable structure
*
* @param array $item API library item
* @param array|object $item API library item
* @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'];
$characters = [];
$links = [];

View File

@ -26,12 +26,13 @@ use Aviat\Ion\Transformer\AbstractTransformer;
final class PersonTransformer extends AbstractTransformer {
/**
* @param array|object $personData
* @param array|object $item
* @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']]
?? array_shift($data['names']['localized']);

View File

@ -29,9 +29,10 @@ use Aviat\Ion\Transformer\AbstractTransformer;
* @return User
*/
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'] ?? [];
$stats = $base['stats'] ?? [];
$waifu = (array_key_exists('waifu', $base)) ? [
@ -71,7 +72,7 @@ final class UserTransformer extends AbstractTransformer {
return $output;
}
private function organizeStats(array $stats, $data = []): array
private function organizeStats(array $stats, array $data = []): array
{
$animeStats = [];
$mangaStats = [];

View File

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

View File

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

View File

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

View File

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

View File

@ -154,7 +154,7 @@ final class AnimeCollection extends BaseController {
public function edit(): void
{
$this->checkAuth();
$this->update($this->request->getParsedBody());
$this->update((array)$this->request->getParsedBody());
}
/**
@ -169,7 +169,7 @@ final class AnimeCollection extends BaseController {
{
$this->checkAuth();
$data = $this->request->getParsedBody();
$data = (array)$this->request->getParsedBody();
if (array_key_exists('id', $data))
{
// Check for existing entry
@ -218,7 +218,7 @@ final class AnimeCollection extends BaseController {
{
$this->checkAuth();
$data = $this->request->getParsedBody();
$data = (array)$this->request->getParsedBody();
if ( ! array_key_exists('hummingbird_id', $data))
{
$this->setFlashMessage("Can't delete item that doesn't exist", 'error');
@ -238,11 +238,11 @@ final class AnimeCollection extends BaseController {
/**
* Update a collection item
*
* @param $data
* @param array $data
* @throws ContainerException
* @throws NotFoundException
*/
protected function update($data): void
protected function update(array $data): void
{
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 bool $display Whether to output the image to the server
* @return void
* @throws NotFoundException
* @throws Throwable
* @throws ContainerException
*/
public function cache(string $type, string $file, $display = TRUE): void
{
@ -134,7 +132,16 @@ final class Images extends BaseController {
[$origWidth] = getimagesizefromstring($data);
$gdImg = imagecreatefromstring($data);
if ($gdImg === FALSE)
{
return;
}
$resizedImg = imagescale($gdImg, $width ?? $origWidth);
if ($resizedImg === FALSE)
{
return;
}
if ($ext === 'gif')
{
@ -161,7 +168,7 @@ final class Images extends BaseController {
? 'image/webp'
: $response->getHeader('content-type')[0];
$outputFile = (strpos($file, '-original') !== FALSE)
$outputFile = (str_contains($file, '-original'))
? "{$filePrefix}-original.{$ext}"
: "{$filePrefix}.{$ext}";

View File

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

View File

@ -74,7 +74,7 @@ final class Misc extends BaseController {
*/
public function loginAction(): void
{
$post = $this->request->getParsedBody();
$post = (array)$this->request->getParsedBody();
if ($this->auth->authenticate($post['password']))
{
@ -83,7 +83,11 @@ final class Misc extends BaseController {
}
$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
{
$post = $this->request->getParsedBody();
$post = (array)$this->request->getParsedBody();
unset($post['settings-tabs']);
$saved = $this->settingsModel->saveSettingsFile($post);
@ -93,7 +93,10 @@ final class Settings extends BaseController {
? $this->setFlashMessage('Saved config settings.', 'success')
: $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('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;
use Aviat\AnimeClient\Enum\EventType;
use function Aviat\Ion\_dir;
use Aura\Router\{Map, Matcher, Route, Rule};
use Aviat\Ion\Json;
use Aura\Router\{
Map,
Matcher,
Route,
Rule,
};
use Aviat\AnimeClient\API\FailedResponseException;
use Aviat\Ion\Di\ContainerInterface;
use Aviat\Ion\Event;
use Aviat\Ion\Friend;
use Aviat\Ion\Type\StringType;
use JetBrains\PhpStorm\ArrayShape;
use LogicException;
use ReflectionException;
use function Aviat\Ion\_dir;
/**
* Basic routing/ dispatch
*/
@ -78,7 +82,7 @@ final class Dispatcher extends RoutingBase {
*
* @return Route|false
*/
public function getRoute()
public function getRoute(): Route | false
{
$logger = $this->container->getLogger();
@ -132,7 +136,7 @@ final class Dispatcher extends RoutingBase {
{
// If not route was matched, return an appropriate http
// error message
$errorRoute = $this->getErrorParams();
$errorRoute = (array)$this->getErrorParams();
$controllerName = DEFAULT_CONTROLLER;
$actionMethod = $errorRoute['action_method'];
$params = $errorRoute['params'];
@ -152,11 +156,11 @@ final class Dispatcher extends RoutingBase {
* Parse out the arguments for the appropriate controller for
* the current route
*
* @param Route $route
* @param Friend $route
* @throws LogicException
* @return array
*/
protected function processRoute($route): array
protected function processRoute(Friend $route): array
{
if ( ! array_key_exists('controller', $route->attributes))
{
@ -166,7 +170,7 @@ final class Dispatcher extends RoutingBase {
$controllerName = $route->attributes['controller'];
// Get the full namespace for a controller if a short name is given
if (strpos($controllerName, '\\') === FALSE)
if ( ! str_contains($controllerName, '\\'))
{
$map = $this->getControllerList();
$controllerName = $map[$controllerName];
@ -191,7 +195,7 @@ final class Dispatcher extends RoutingBase {
$logger = $this->container->getLogger();
if ($logger !== NULL)
{
$logger->info(json_encode($params));
$logger->info(Json::encode($params));
}
return [
@ -244,6 +248,10 @@ final class Dispatcher extends RoutingBase {
$path = trim($path, '/');
$actualPath = realpath(_dir(SRC_DIR, $path));
$classFiles = glob("{$actualPath}/*.php");
if ($classFiles === FALSE)
{
return [];
}
$controllers = [];
@ -282,9 +290,10 @@ final class Dispatcher extends RoutingBase {
$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;
$controller = new $controllerName($this->container);

View File

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

View File

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

View File

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

View File

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

View File

@ -29,15 +29,9 @@ class Collection extends DB {
/**
* The query builder object
* @var QueryBuilderInterface
* @var QueryBuilderInterface|null
*/
protected QueryBuilderInterface $db;
/**
* Whether the database is valid for querying
* @var boolean
*/
protected bool $validDatabase = FALSE;
protected ?QueryBuilderInterface $db;
/**
* Create a new collection object
@ -51,9 +45,8 @@ class Collection extends DB {
try
{
$this->db = Query($this->dbConfig);
$this->validDatabase = TRUE;
}
catch (PDOException $e)
catch (PDOException)
{
$this->db = Query([
'type' => 'sqlite',
@ -67,19 +60,12 @@ class Collection extends DB {
{
$dbFileName = $this->dbConfig['file'];
if ($dbFileName !== ':memory:' && file_exists($dbFileName))
if ($dbFileName !== ':memory:')
{
$dbFile = file_get_contents($dbFileName);
$this->validDatabase = (strpos($dbFile, 'SQLite format 3') === 0);
$rawFile = file_get_contents($dbFileName);
$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
* @return AnimeListItem|MangaListItem
*/
public function getLibraryItem(string $itemId)
public function getLibraryItem(string $itemId): AnimeListItem|MangaListItem
{
return $this->kitsuModel->getListItem($itemId);
}
@ -100,7 +100,13 @@ trait MediaTrait {
public function createLibraryItem(array $data): bool
{
$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)
{

View File

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

View File

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

View File

@ -23,10 +23,10 @@ abstract class AbstractType implements ArrayAccess, Countable {
/**
* Populate values for un-serializing data
*
* @param $properties
* @param mixed $properties
* @return self
*/
public static function __set_state($properties): self
public static function __set_state(mixed $properties): self
{
return new static($properties);
}
@ -43,7 +43,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
if (get_parent_class($currentClass) !== FALSE)
{
return (new $currentClass($data))->toArray();
return static::class::from($data)->toArray();
}
return NULL;
@ -55,7 +55,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* @param mixed $data
* @return static
*/
final public static function from($data): self
final public static function from(mixed $data): static
{
return new static($data);
}
@ -65,7 +65,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
*
* @param mixed $data
*/
final private function __construct($data = [])
final private function __construct(mixed $data = [])
{
$typeKeys = array_keys((array)$this);
$dataKeys = array_keys((array)$data);
@ -87,10 +87,10 @@ abstract class AbstractType implements ArrayAccess, Countable {
/**
* See if a property is set
*
* @param $name
* @param string $name
* @return bool
*/
final public function __isset($name): bool
final public function __isset(string $name): bool
{
return property_exists($this, $name) && isset($this->$name);
}
@ -102,7 +102,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* @param mixed $value
* @return void
*/
final public function __set($name, $value): void
final public function __set(string $name, mixed $value): void
{
$setterMethod = 'set' . ucfirst($name);
@ -128,7 +128,7 @@ abstract class AbstractType implements ArrayAccess, Countable {
* @param string $name
* @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
// values to reasonable defaults, and not have to resort to array indexes
@ -148,46 +148,47 @@ abstract class AbstractType implements ArrayAccess, Countable {
/**
* Implementing ArrayAccess
*
* @param $offset
* @param mixed $offset
* @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
*
* @param $offset
* @param mixed $offset
* @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
*
* @param $offset
* @param $value
* @param mixed $offset
* @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
*
* @param $offset
* @param mixed $offset
*/
final public function offsetUnset($offset): void
final public function offsetUnset(mixed $offset): void
{
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
{
$keys = array_keys($this->toArray());
$keys = array_keys((array)$this->toArray());
return count($keys);
}
/**
* Recursively cast properties to an array
*
* Returns early on primitive values to work recursively.
*
* @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;
@ -223,27 +251,9 @@ abstract class AbstractType implements ArrayAccess, Countable {
{
$output[$key] = (is_scalar($value) || empty($value))
? $value
: $this->toArray((array) $value);
: $this->fromObject((array) $value);
}
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 = [];
/**
* @var string|int
* @var string
*/
public $id = '';
public string $id = '';
public array $included = [];

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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
* @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);
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 $fileOptions - Options to pass to file_get_contents
* @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);
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
* @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
if ($json === NULL)
@ -89,9 +92,11 @@ class Json {
* @throws JsonException
* @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);
}

View File

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

View File

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

View File

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

View File

@ -27,7 +27,7 @@ class StringType extends Stringy {
* Alias for `create` static constructor
*
* @param string $str
* @return $this
* @return 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\ContainerInterface;
use Aviat\Ion\Di\Exception\ContainerException;
use Aviat\Ion\Di\Exception\NotFoundException;
use Laminas\Diactoros\Response\HtmlResponse;
use const EXTR_OVERWRITE;
@ -40,8 +38,6 @@ class HtmlView extends HttpView {
* Create the Html View
*
* @param ContainerInterface $container
* @throws ContainerException
* @throws NotFoundException
*/
public function __construct(ContainerInterface $container)
{
@ -57,6 +53,7 @@ class HtmlView extends HttpView {
* @param string $path
* @param array $data
* @return string
* @throws \Throwable
*/
public function renderTemplate(string $path, array $data): string
{
@ -69,13 +66,12 @@ class HtmlView extends HttpView {
ob_start();
extract($data, EXTR_OVERWRITE);
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
$buffer = preg_replace('/>\s+</', '> <', $buffer);
return $buffer;
return preg_replace('/>\s+</', '> <', $buffer) ?? $buffer;
}
}
// End of HtmlView.php

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -122,4 +122,36 @@ class KitsuTest extends TestCase {
$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 {
public function transform($item)
public function transform(array|object $item): array
{
$out = [];
$genre_list = (array) $item;