More type fixes!

This commit is contained in:
Buster Neece 2024-01-14 13:20:01 -06:00
parent c2f352c115
commit bbf8d56b46
No known key found for this signature in database
26 changed files with 149 additions and 84 deletions

View File

@ -12,7 +12,7 @@ $environment = App\AppFactory::buildEnvironment();
$console = new Symfony\Component\Console\Application(
'AzuraCast installer',
App\Version::FALLBACK_VERSION
App\Version::STABLE_VERSION
);
$installCommand = new App\Installer\Command\InstallCommand();

View File

@ -330,6 +330,10 @@ return [
// Register plugin-provided message queue receivers
$receivers = $plugins->registerMessageQueueReceivers($receivers);
/**
* @var class-string $messageClass
* @var class-string $handlerClass
*/
foreach ($receivers as $messageClass => $handlerClass) {
$handlers[$messageClass][] = static function ($message) use ($handlerClass, $di) {
$obj = $di->get($handlerClass);

View File

@ -19,12 +19,30 @@ use Psr\EventDispatcher\EventDispatcherInterface;
use function in_array;
use function is_array;
/**
* @phpstan-type PermissionsArray array{
* global: array<string, string>,
* station: array<string, string>
* }
*/
final class Acl
{
use RequestAwareTrait;
/**
* @var PermissionsArray
*/
private array $permissions;
/**
* @var null|array<
* int,
* array{
* stations?: array<int, array<string>>,
* global?: array<string>
* }
* >
*/
private ?array $actions;
public function __construct(
@ -41,12 +59,12 @@ final class Acl
{
$sql = $this->em->createQuery(
<<<'DQL'
SELECT rp FROM App\Entity\RolePermission rp
SELECT rp.station_id, rp.role_id, rp.action_name FROM App\Entity\RolePermission rp
DQL
);
$this->actions = [];
foreach ($sql->getArrayResult() as $row) {
foreach ($sql->toIterable() as $row) {
if ($row['station_id']) {
$this->actions[$row['role_id']]['stations'][$row['station_id']][] = $row['action_name'];
} else {
@ -69,12 +87,11 @@ final class Acl
}
/**
* @return mixed[]
* @return array
*/
public function listPermissions(): array
{
if (!isset($this->permissions)) {
/** @var array<string,array<string, string>> $permissions */
$permissions = [
'global' => [],
'station' => [],

View File

@ -8,6 +8,7 @@ use App\Container\EnvironmentAwareTrait;
use App\Entity\Repository\UserRepository;
use App\Entity\User;
use App\Exception\NotLoggedInException;
use App\Utilities\Types;
use Mezzio\Session\SessionInterface;
final class Auth
@ -83,7 +84,7 @@ final class Auth
if (!$this->session->has(self::SESSION_MASQUERADE_USER_ID_KEY)) {
$this->masqueraded_user = false;
} else {
$maskUserId = (int)$this->session->get(self::SESSION_MASQUERADE_USER_ID_KEY);
$maskUserId = Types::int($this->session->get(self::SESSION_MASQUERADE_USER_ID_KEY));
if (0 !== $maskUserId) {
$user = $this->userRepo->getRepository()->find($maskUserId);
} else {
@ -125,7 +126,7 @@ final class Auth
*/
public function isLoginComplete(): bool
{
return $this->session->get(self::SESSION_IS_LOGIN_COMPLETE_KEY, false) ?? false;
return Types::bool($this->session->get(self::SESSION_IS_LOGIN_COMPLETE_KEY, false));
}
/**
@ -136,7 +137,7 @@ final class Auth
public function getUser(): ?User
{
if (null === $this->user) {
$userId = (int)$this->session->get(self::SESSION_USER_ID_KEY);
$userId = Types::int($this->session->get(self::SESSION_USER_ID_KEY));
if (0 === $userId) {
$this->user = false;

View File

@ -6,6 +6,7 @@ namespace App\Cache;
use App\Entity\Api\NowPlaying\NowPlaying;
use App\Entity\Station;
use App\Utilities\Types;
use Psr\Cache\CacheItemInterface;
use Psr\Cache\CacheItemPoolInterface;
@ -41,9 +42,13 @@ final class NowPlayingCache
$stationCacheItem = $this->getStationCache($station);
return ($stationCacheItem->isHit())
? $stationCacheItem->get()
: null;
if (!$stationCacheItem->isHit()) {
return null;
}
$np = $stationCacheItem->get();
assert($np instanceof NowPlaying);
return $np;
}
/**
@ -78,11 +83,18 @@ final class NowPlayingCache
return $np;
}
/**
* @return array<int, array{
* short_name: string,
* is_public: bool,
* updated_at: int
* }>
*/
public function getLookup(): array
{
$lookupCacheItem = $this->getLookupCache();
return $lookupCacheItem->isHit()
? (array)$lookupCacheItem->get()
? Types::array($lookupCacheItem->get())
: [];
}
@ -114,7 +126,7 @@ final class NowPlayingCache
$lookupCacheItem = $this->getLookupCache();
$lookupCache = $lookupCacheItem->isHit()
? (array)$lookupCacheItem->get()
? Types::array($lookupCacheItem->get())
: [];
$lookupCache[$station->getIdRequired()] = [
@ -131,12 +143,9 @@ final class NowPlayingCache
private function getStationCache(string $identifier): CacheItemInterface
{
if (is_numeric($identifier)) {
$lookupCacheItem = $this->getLookupCache();
$lookupCache = $lookupCacheItem->isHit()
? (array)$lookupCacheItem->get()
: [];
$lookupCache = $this->getLookup();
$identifier = (int)$identifier;
$identifier = Types::int($identifier);
if (isset($lookupCache[$identifier])) {
$identifier = $lookupCache[$identifier]['short_name'];
}

View File

@ -51,7 +51,7 @@ final class GenerateApiDocsCommand extends CommandAbstract
define('AZURACAST_API_NAME', 'AzuraCast Public Demo Server');
define(
'AZURACAST_VERSION',
$useCurrentVersion ? $this->version->getVersion() : Version::FALLBACK_VERSION
$useCurrentVersion ? $this->version->getVersion() : Version::STABLE_VERSION
);
$finder = Util::finder(

View File

@ -206,7 +206,8 @@ final class CloneAction extends StationsController implements SingleActionInterf
}
/**
* @param Collection<int, mixed> $collection
* @template T of mixed
* @param Collection<int, T> $collection
*/
private function cloneCollection(
Collection $collection,

View File

@ -86,6 +86,7 @@ final class DownloadAction implements SingleActionInterface
$csv->insertOne($headerRow);
/** @var array $row */
foreach ($query->getArrayResult() as $row) {
$customFieldsById = [];
foreach ($row['custom_fields'] ?? [] as $rowCustomField) {

View File

@ -28,6 +28,7 @@ use App\Radio\Backend\Liquidsoap;
use App\Radio\Enums\BackendAdapters;
use App\Radio\Enums\LiquidsoapQueues;
use App\Utilities\File;
use App\Utilities\Types;
use Exception;
use InvalidArgumentException;
use League\Flysystem\StorageAttributes;
@ -411,8 +412,12 @@ final class BatchAction implements SingleActionInterface
ExtendedFilesystemInterface $fs,
bool $recursive = false
): BatchResult {
$files = array_values((array)$request->getParam('files', []));
$directories = array_values((array)$request->getParam('dirs', []));
$files = array_values(
Types::array($request->getParam('files', []))
);
$directories = array_values(
Types::array($request->getParam('dirs', []))
);
if ($recursive) {
foreach ($directories as $dir) {

View File

@ -9,6 +9,7 @@ use App\Entity\Api\Error;
use App\Flysystem\StationFilesystems;
use App\Http\Response;
use App\Http\ServerRequest;
use App\Utilities\Types;
use Psr\Http\Message\ResponseInterface;
final class DownloadAction implements SingleActionInterface
@ -29,7 +30,7 @@ final class DownloadAction implements SingleActionInterface
$fsMedia = $this->stationFilesystems->getMediaFilesystem($station);
$path = $request->getParam('file');
$path = Types::string($request->getParam('file'));
if (!$fsMedia->fileExists($path)) {
return $response->withStatus(404)

View File

@ -20,6 +20,7 @@ use App\Http\Response;
use App\Http\ServerRequest;
use App\Media\MediaProcessor;
use App\Service\Flow;
use App\Utilities\Types;
use Psr\Http\Message\ResponseInterface;
final class FlowUploadAction implements SingleActionInterface
@ -51,7 +52,7 @@ final class FlowUploadAction implements SingleActionInterface
return $flowResponse;
}
$currentDir = $request->getParam('currentDirectory', '');
$currentDir = Types::string($request->getParam('currentDirectory'));
$destPath = $flowResponse->getClientFilename();
if (!empty($currentDir)) {

View File

@ -52,10 +52,10 @@ final class ListAction implements SingleActionInterface
$fs = $this->stationFilesystems->getMediaFilesystem($station);
$currentDir = $request->getParam('currentDirectory', '');
$currentDir = Types::string($request->getParam('currentDirectory'));
$searchPhraseFull = trim($request->getParam('searchPhrase', ''));
$isSearch = !empty($searchPhraseFull);
$searchPhraseFull = Types::stringOrNull($request->getParam('searchPhrase'), true);
$isSearch = null !== $searchPhraseFull;
[$searchPhrase, $playlist, $special] = $this->parseSearchQuery(
$station,
@ -74,7 +74,7 @@ final class ListAction implements SingleActionInterface
$cacheKey = implode('.', $cacheKeyParts);
$flushCache = (bool)$request->getParam('flushCache', false);
$flushCache = Types::bool($request->getParam('flushCache'));
if (!$flushCache && $this->cache->has($cacheKey)) {
/** @var array<int, FileList> $result */

View File

@ -61,7 +61,7 @@ trait CanSortResults
}
/**
* @return array{string, string}
* @return array{string|null, Criteria::ASC|Criteria::DESC}
*/
protected function getSortFromRequest(
ServerRequest $request,
@ -69,7 +69,7 @@ trait CanSortResults
): array {
$sortOrder = Types::stringOrNull($request->getParam('sortOrder'), true) ?? $defaultSortOrder;
return [
$request->getParam('sort'),
Types::stringOrNull($request->getParam('sort'), true),
(Criteria::DESC === strtoupper($sortOrder))
? Criteria::DESC
: Criteria::ASC,

View File

@ -41,10 +41,9 @@ trait HasMediaSearch
FROM App\Entity\StationPlaylist sp
WHERE sp.station = :station
DQL
)->setParameter('station', $station)
->getArrayResult();
)->setParameter('station', $station);
foreach ($playlistNameLookupRaw as $playlistRow) {
foreach ($playlistNameLookupRaw->toIterable() as $playlistRow) {
$shortName = StationPlaylist::generateShortName($playlistRow['name']);
if ($shortName === $playlistId) {
$playlistId = $playlistRow['id'];

View File

@ -8,8 +8,10 @@ use JsonSerializable;
final class BatchResult implements JsonSerializable
{
/** @var string[] */
public array $files = [];
/** @var string[] */
public array $directories = [];
public array $errors = [];

View File

@ -4,23 +4,33 @@ declare(strict_types=1);
namespace App\Event;
use App\Acl;
use Symfony\Contracts\EventDispatcher\Event;
/**
* @phpstan-import-type PermissionsArray from Acl
*/
final class BuildPermissions extends Event
{
/**
* @param PermissionsArray $permissions
*/
public function __construct(
private array $permissions
) {
}
/**
* @return mixed[]
* @return PermissionsArray
*/
public function getPermissions(): array
{
return $this->permissions;
}
/**
* @param PermissionsArray $permissions
*/
public function setPermissions(array $permissions): void
{
$this->permissions = $permissions;

View File

@ -56,7 +56,7 @@ final class RemoteAlbumArt
[
RequestOptions::TIMEOUT => 10,
RequestOptions::HEADERS => [
'User-Agent' => 'AzuraCast ' . Version::FALLBACK_VERSION,
'User-Agent' => 'AzuraCast ' . Version::STABLE_VERSION,
],
]
);

View File

@ -43,7 +43,7 @@ final class AzuraCastCentral
if ($commitHash) {
$requestBody['version'] = $commitHash;
} else {
$requestBody['release'] = Version::FALLBACK_VERSION;
$requestBody['release'] = Version::STABLE_VERSION;
}
$this->logger->debug(

View File

@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Service\Dropbox;
use App\Entity\StorageLocation;
use App\Utilities\Types;
use GuzzleHttp\Exception\ClientException;
use Psr\Cache\CacheItemPoolInterface;
use Spatie\Dropbox\RefreshableTokenProvider;
@ -80,7 +81,7 @@ final class OAuthAdapter implements RefreshableTokenProvider
$this->psr6Cache->save($cacheItem);
}
return $cacheItem->get();
return Types::string($cacheItem->get());
}
private function getOauthProvider(): OAuthProvider

View File

@ -81,7 +81,7 @@ final class LastFm
[
RequestOptions::HTTP_ERRORS => true,
RequestOptions::HEADERS => [
'User-Agent' => 'AzuraCast ' . Version::FALLBACK_VERSION,
'User-Agent' => 'AzuraCast ' . Version::STABLE_VERSION,
'Accept' => 'application/json',
],
RequestOptions::QUERY => $query,

View File

@ -65,7 +65,7 @@ final class MusicBrainz
RequestOptions::TIMEOUT => 7,
RequestOptions::HTTP_ERRORS => true,
RequestOptions::HEADERS => [
'User-Agent' => 'AzuraCast ' . Version::FALLBACK_VERSION,
'User-Agent' => 'AzuraCast ' . Version::STABLE_VERSION,
'Accept' => 'application/json',
],
RequestOptions::QUERY => $query,
@ -157,7 +157,7 @@ final class MusicBrainz
[
RequestOptions::ALLOW_REDIRECTS => true,
RequestOptions::HEADERS => [
'User-Agent' => 'AzuraCast ' . Version::FALLBACK_VERSION,
'User-Agent' => 'AzuraCast ' . Version::STABLE_VERSION,
'Accept' => 'application/json',
],
]

View File

@ -6,6 +6,7 @@ namespace App\Session;
use App\Environment;
use App\Exception;
use App\Utilities\Types;
use Mezzio\Session\SessionInterface;
final class Csrf
@ -32,9 +33,9 @@ final class Csrf
{
$sessionKey = $this->getSessionIdentifier($namespace);
if ($this->session->has($sessionKey)) {
$csrf = $this->session->get($sessionKey);
if (!empty($csrf)) {
return (string)$csrf;
$csrf = Types::stringOrNull($this->session->get($sessionKey), true);
if (null !== $csrf) {
return $csrf;
}
}
@ -71,9 +72,8 @@ final class Csrf
throw new Exception\CsrfValidationException('No CSRF token supplied for this namespace.');
}
$sessionKey = $this->session->get($sessionIdentifier);
if (0 !== strcmp($key, (string)$sessionKey)) {
$sessionKey = Types::string($this->session->get($sessionIdentifier));
if (0 !== strcmp($key, $sessionKey)) {
throw new Exception\CsrfValidationException('Invalid CSRF token supplied.');
}
}

View File

@ -14,11 +14,19 @@ use Throwable;
/**
* App Core Framework Version
*
* @phpstan-type VersionDetails array{
* commit: string|null,
* commit_short: string,
* commit_timestamp: int,
* commit_date: string,
* branch: string|null,
* }
*/
final class Version
{
/** @var string Version that is displayed if no Git repository information is present. */
public const FALLBACK_VERSION = '0.19.4';
/** @var string The current latest stable version. */
public const STABLE_VERSION = '0.19.4';
private string $repoDir;
@ -41,19 +49,10 @@ final class Version
: ReleaseChannel::RollingRelease;
}
/**
* @return string The current tagged version.
*/
public function getVersion(): string
{
$details = $this->getDetails();
return $details['tag'] ?? self::FALLBACK_VERSION;
}
/**
* Load cache or generate new repository details from the underlying Git repository.
*
* @return string[]
* @return VersionDetails
*/
public function getDetails(): array
{
@ -63,12 +62,16 @@ final class Version
$details = $this->cache->get('app_version_details');
if (empty($details)) {
$details = $this->getRawDetails();
$rawDetails = $this->getRawDetails();
$details['commit_short'] = substr($details['commit'] ?? '', 0, 7);
$details = [
'commit' => $rawDetails['commit'],
'commit_short' => substr($rawDetails['commit'] ?? '', 0, 7),
'branch' => $rawDetails['branch'],
];
if (!empty($details['commit_date_raw'])) {
$commitDate = new DateTime($details['commit_date_raw']);
if (!empty($rawDetails['commit_date_raw'])) {
$commitDate = new DateTime($rawDetails['commit_date_raw']);
$commitDate->setTimezone(new DateTimeZone('UTC'));
$details['commit_timestamp'] = $commitDate->getTimestamp();
@ -78,8 +81,6 @@ final class Version
$details['commit_date'] = 'N/A';
}
$details['tag'] = self::FALLBACK_VERSION;
$ttl = $this->environment->isProduction() ? 86400 : 600;
$this->cache->set('app_version_details', $details, $ttl);
@ -92,7 +93,11 @@ final class Version
/**
* Generate new repository details from the underlying Git repository.
*
* @return mixed[]
* @return array{
* commit: string|null,
* commit_date_raw: string|null,
* branch: string|null
* }
*/
private function getRawDetails(): array
{
@ -120,7 +125,11 @@ final class Version
];
}
return [];
return [
'commit' => null,
'commit_date_raw' => null,
'branch' => null,
];
}
/**
@ -145,8 +154,9 @@ final class Version
public function getVersionText(): string
{
$details = $this->getDetails();
$releaseChannel = $this->getReleaseChannelEnum();
if (isset($details['tag'])) {
if (ReleaseChannel::RollingRelease === $releaseChannel) {
$commitLink = 'https://github.com/AzuraCast/AzuraCast/commit/' . $details['commit'];
$commitText = sprintf(
'#<a href="%s" target="_blank">%s</a> (%s)',
@ -155,14 +165,10 @@ final class Version
$details['commit_date']
);
$releaseChannel = $this->getReleaseChannelEnum();
if (ReleaseChannel::RollingRelease === $releaseChannel) {
return 'Rolling Release ' . $commitText;
}
return 'v' . $details['tag'] . ' Stable';
return 'Rolling Release ' . $commitText;
}
return 'v' . self::FALLBACK_VERSION . ' Release Build';
return 'v' . self::STABLE_VERSION . ' Stable';
}
/**
@ -171,15 +177,20 @@ final class Version
public function getCommitHash(): ?string
{
$details = $this->getDetails();
return $details['commit'] ?? null;
return $details['commit'];
}
/**
* @return string|null The shortened Git hash corresponding to the current commit.
* @return string The shortened Git hash corresponding to the current commit.
*/
public function getCommitShort(): ?string
public function getCommitShort(): string
{
$details = $this->getDetails();
return $details['commit_short'] ?? null;
return $details['commit_short'];
}
public function getVersion(): string
{
return self::STABLE_VERSION;
}
}

View File

@ -7,7 +7,9 @@ namespace App\Webhook\Connector;
use App\Container\LoggerAwareTrait;
use App\Entity\Api\NowPlaying\NowPlaying;
use App\Entity\StationWebhook;
use App\Utilities;
use App\Utilities\Arrays;
use App\Utilities\Types;
use App\Utilities\Urls;
use GuzzleHttp\Client;
use InvalidArgumentException;
use Psr\Http\Message\ResponseInterface;
@ -87,7 +89,7 @@ abstract class AbstractConnector implements ConnectorInterface
*/
public function replaceVariables(array $rawVars, NowPlaying $np): array
{
$values = Utilities\Arrays::flattenArray($np);
$values = Arrays::flattenArray($np);
$vars = [];
foreach ($rawVars as $varKey => $varValue) {
@ -96,7 +98,7 @@ abstract class AbstractConnector implements ConnectorInterface
"/\{\{(\s*)([a-zA-Z\d\-_.]+)(\s*)}}/",
static function (array $matches) use ($values): string {
$innerValue = strtolower(trim($matches[2]));
return $values[$innerValue] ?? '';
return Types::string($values[$innerValue] ?? '');
},
$varValue
);
@ -110,9 +112,9 @@ abstract class AbstractConnector implements ConnectorInterface
*/
protected function getValidUrl(mixed $urlString = null): ?string
{
$urlString = Utilities\Types::stringOrNull($urlString, true);
$urlString = Types::stringOrNull($urlString, true);
$uri = Utilities\Urls::tryParseUserUrl(
$uri = Urls::tryParseUserUrl(
$urlString,
'Webhook'
);

View File

@ -18,7 +18,7 @@ final class Mastodon extends AbstractSocialConnector
protected function getRateLimitTime(StationWebhook $webhook): ?int
{
$config = $webhook->getConfig();
$rateLimitSeconds = (int)($config['rate_limit'] ?? 0);
$rateLimitSeconds = Types::int($config['rate_limit'] ?? null);
return max(10, $rateLimitSeconds);
}

View File

@ -8,7 +8,7 @@ ini_set('display_errors', 1);
require dirname(__DIR__) . '/vendor/autoload.php';
const AZURACAST_VERSION = App\Version::FALLBACK_VERSION;
const AZURACAST_VERSION = App\Version::STABLE_VERSION;
const AZURACAST_API_URL = 'https://localhost/api';
const AZURACAST_API_NAME = 'Testing API';