[TOOLS] Continue raising PHPStan level to 6

This commit is contained in:
Hugo Sales 2022-10-19 22:39:17 +01:00
parent c31f3d4997
commit 2fd46ca886
Signed by: someonewithpc
GPG Key ID: 7D0C7EAFC9D835A0
89 changed files with 646 additions and 278 deletions

View File

@ -178,7 +178,7 @@ return $config
// There MUST NOT be a space after the opening parenthesis. There MUST NOT be a space before the closing parenthesis.
'no_spaces_inside_parenthesis' => true,
// Removes `@param`, `@return` and `@var` tags that don't provide any useful information.
'no_superfluous_phpdoc_tags' => true,
'no_superfluous_phpdoc_tags' => false,
// Remove trailing commas in list function calls.
'no_trailing_comma_in_list_call' => true,
// PHP single-line arrays should not have trailing comma.

View File

@ -54,7 +54,7 @@ use Symfony\Component\HttpFoundation\Request;
*/
class Circle extends Component
{
/** @phpstan-use MetaCollectionTrait<Circle> */
/** @phpstan-use MetaCollectionTrait<ActorCircle> */
use MetaCollectionTrait;
public const TAG_CIRCLE_REGEX = '/' . Nickname::BEFORE_MENTIONS . '@#([\pL\pN_\-\.]{1,64})/';
protected const SLUG = 'circle';
@ -228,7 +228,7 @@ class Circle extends Component
* @param null|array<string, mixed> $vars Page vars sent by AppendRightPanelBlock event
* @param bool $ids_only true if only the Collections ids are to be returned
*
* @return Circle[]|int[]
* @return ($ids_only is true ? int[] : ActorCircle[])
*/
protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array
{

View File

@ -90,14 +90,12 @@ class Circles extends MetaCollectionController
return $this->getCollectionItems($tagger_id, $circle_id);
}
/**
* @return ActorCircle[]
*/
public function getCollectionsByActorId(int $owner_id): array
{
return DB::findBy(ActorCircle::class, ['tagger' => $owner_id], order_by: ['id' => 'desc']);
}
public function getCollectionBy(int $owner_id, int $collection_id): ActorCircle
public function getCollectionBy(int $owner_id, int $collection_id): self
{
return DB::findOneBy(ActorCircle::class, ['id' => $collection_id, 'actor_id' => $owner_id]);
}

View File

@ -26,7 +26,8 @@ class Collection extends Component
*
* @param array<string, OrderByType> $note_order_by
* @param array<string, OrderByType> $actor_order_by
* @return array{notes: Note[], actors: Actor[]}
*
* @return array{notes: null|Note[], actors: null|Actor[]}
*/
public static function query(string $query, int $page, ?string $locale = null, ?Actor $actor = null, array $note_order_by = [], array $actor_order_by = []): array
{
@ -84,6 +85,9 @@ class Collection extends Component
/**
* Convert $term to $note_expr and $actor_expr, search criteria. Handles searching for text
* notes, for different types of actors and for the content of text notes
*
* @param mixed $note_expr
* @param mixed $actor_expr
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): EventResult
{
@ -91,50 +95,50 @@ class Collection extends Component
$term = explode(':', $term);
if (Formatting::startsWith($term[0], 'note')) {
switch ($term[0]) {
case 'notes-all':
$note_expr = $eb->neq('note.created', null);
break;
case 'note-local':
$note_expr = $eb->eq('note.is_local', filter_var($term[1], \FILTER_VALIDATE_BOOLEAN));
break;
case 'note-types':
case 'notes-include':
case 'note-filter':
if (\is_null($note_expr)) {
$note_expr = [];
}
if (array_intersect(explode(',', $term[1]), ['text', 'words']) !== []) {
$note_expr[] = $eb->neq('note.content', null);
} else {
$note_expr[] = $eb->eq('note.content', null);
}
break;
case 'note-conversation':
$note_expr = $eb->eq('note.conversation_id', (int) trim($term[1]));
break;
case 'note-from':
case 'notes-from':
$subscribed_expr = $eb->eq('subscription.subscriber_id', $actor->getId());
$type_consts = [];
if ($term[1] === 'subscribed') {
$type_consts = null;
}
foreach (explode(',', $term[1]) as $from) {
if (str_starts_with($from, 'subscribed-')) {
[, $type] = explode('-', $from);
if (\in_array($type, ['actor', 'actors'])) {
$type_consts = null;
} else {
$type_consts[] = \constant(Actor::class . '::' . mb_strtoupper($type === 'organisation' ? 'group' : $type));
case 'notes-all':
$note_expr = $eb->neq('note.created', null);
break;
case 'note-local':
$note_expr = $eb->eq('note.is_local', filter_var($term[1], \FILTER_VALIDATE_BOOLEAN));
break;
case 'note-types':
case 'notes-include':
case 'note-filter':
if (\is_null($note_expr)) {
$note_expr = [];
}
if (array_intersect(explode(',', $term[1]), ['text', 'words']) !== []) {
$note_expr[] = $eb->neq('note.content', null);
} else {
$note_expr[] = $eb->eq('note.content', null);
}
break;
case 'note-conversation':
$note_expr = $eb->eq('note.conversation_id', (int) trim($term[1]));
break;
case 'note-from':
case 'notes-from':
$subscribed_expr = $eb->eq('subscription.subscriber_id', $actor->getId());
$type_consts = [];
if ($term[1] === 'subscribed') {
$type_consts = null;
}
foreach (explode(',', $term[1]) as $from) {
if (str_starts_with($from, 'subscribed-')) {
[, $type] = explode('-', $from);
if (\in_array($type, ['actor', 'actors'])) {
$type_consts = null;
} else {
$type_consts[] = \constant(Actor::class . '::' . mb_strtoupper($type === 'organisation' ? 'group' : $type));
}
}
}
}
if (\is_null($type_consts)) {
$note_expr = $subscribed_expr;
} elseif (!empty($type_consts)) {
$note_expr = $eb->andX($subscribed_expr, $eb->in('note_actor.type', $type_consts));
}
break;
if (\is_null($type_consts)) {
$note_expr = $subscribed_expr;
} elseif (!empty($type_consts)) {
$note_expr = $eb->andX($subscribed_expr, $eb->in('note_actor.type', $type_consts));
}
break;
}
} elseif (Formatting::startsWith($term, 'actor-')) {
switch ($term[0]) {
@ -148,8 +152,8 @@ class Collection extends Component
foreach (
[
Actor::PERSON => ['person', 'people'],
Actor::GROUP => ['group', 'groups', 'org', 'orgs', 'organisation', 'organisations', 'organization', 'organizations'],
Actor::BOT => ['bot', 'bots'],
Actor::GROUP => ['group', 'groups', 'org', 'orgs', 'organisation', 'organisations', 'organization', 'organizations'],
Actor::BOT => ['bot', 'bots'],
] as $type => $match) {
if (array_intersect(explode(',', $term[1]), $match) !== []) {
$actor_expr[] = $eb->eq('actor.type', $type);

View File

@ -4,6 +4,9 @@ declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
/**
* @extends OrderedCollection<\Component\Circle\Entity\ActorCircle>
*/
class CircleController extends OrderedCollection
{
}

View File

@ -6,18 +6,20 @@ namespace Component\Collection\Util\Controller;
use App\Core\Controller;
use App\Entity\Actor;
use App\Entity\Note;
use App\Util\Common;
use Component\Collection\Collection as CollectionComponent;
/**
* @template T
*/
class Collection extends Controller
abstract class Collection extends Controller
{
/**
* @param array<string, OrderByType> $note_order_by
* @param array<string, OrderByType> $actor_order_by
* @return array<T>
*
* @return array{notes: null|Note[], actors: null|Actor[]}
*/
public function query(string $query, ?string $locale = null, ?Actor $actor = null, array $note_order_by = [], array $actor_order_by = []): array
{

View File

@ -38,6 +38,11 @@ use App\Entity\Note;
use App\Util\Common;
use Functional as F;
/**
* @template T
*
* @extends OrderedCollection<T>
*/
abstract class FeedController extends OrderedCollection
{
/**
@ -45,9 +50,11 @@ abstract class FeedController extends OrderedCollection
* notes or actors the user specified, as well as format the raw
* list of notes into a usable format
*
* @template T of Note|Actor
* @param T[] $result
* @return T[]
* @template NA of Note|Actor
*
* @param NA[] $result
*
* @return NA[]
*/
protected function postProcess(array $result): array
{

View File

@ -39,10 +39,13 @@ use App\Util\Common;
use App\Util\Exception\RedirectException;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormView;
use Symfony\Component\HttpFoundation\Request;
/**
* @template T
* @template T of object
*
* @extends FeedController<T>
*/
abstract class MetaCollectionController extends FeedController
{
@ -70,7 +73,7 @@ abstract class MetaCollectionController extends FeedController
abstract public function createCollection(int $owner_id, string $name): bool;
/**
* @return T[]
* @return ControllerResultType
*/
public function collectionsViewByActorNickname(Request $request, string $nickname): array
{
@ -79,7 +82,7 @@ abstract class MetaCollectionController extends FeedController
}
/**
* @return T[]
* @return ControllerResultType
*/
public function collectionsViewByActorId(Request $request, int $id): array
{
@ -135,34 +138,23 @@ abstract class MetaCollectionController extends FeedController
// the functions and passing that class to the template.
// This is suggested at https://web.archive.org/web/20220226132328/https://stackoverflow.com/questions/3595727/twig-pass-function-into-template/50364502
$fn = new class($id, $nickname, $request, $this, static::SLUG) {
private $id;
private $nick;
private $request;
private $parent;
private $slug;
public function __construct($id, $nickname, $request, $parent, $slug)
public function __construct(private int $id, private string $nickname, private Request $request, private object $parent, private string $slug)
{
$this->id = $id;
$this->nick = $nickname;
$this->request = $request;
$this->parent = $parent;
$this->slug = $slug;
}
// there's already an injected function called path,
// that maps to Router::url(name, args), but since
// I want to preserve nicknames, I think it's better
// to use that getUrl function
public function getUrl($cid)
public function getUrl(int $cid): string
{
return $this->parent->getCollectionUrl($this->id, $this->nick, $cid);
return $this->parent->getCollectionUrl($this->id, $this->nickname, $cid);
}
// There are many collections in this page and we need two
// forms for each one of them: one form to edit the collection's
// name and another to remove the collection.
// creating the edit form
public function editForm($collection)
public function editForm(object $collection): FormView
{
$edit = Form::create([
['name', TextType::class, [
@ -181,7 +173,7 @@ abstract class MetaCollectionController extends FeedController
]);
$edit->handleRequest($this->request);
if ($edit->isSubmitted() && $edit->isValid()) {
$this->parent->setCollectionName($this->id, $this->nick, $collection, $edit->getData()['name']);
$this->parent->setCollectionName($this->id, $this->nickname, $collection, $edit->getData()['name']);
DB::flush();
throw new RedirectException();
}
@ -189,7 +181,7 @@ abstract class MetaCollectionController extends FeedController
}
// creating the remove form
public function rmForm($collection)
public function rmForm(object $collection): FormView
{
$rm = Form::create([
['remove_' . $collection->getId(), SubmitType::class, [
@ -202,7 +194,7 @@ abstract class MetaCollectionController extends FeedController
]);
$rm->handleRequest($this->request);
if ($rm->isSubmitted()) {
$this->parent->removeCollection($this->id, $this->nick, $collection);
$this->parent->removeCollection($this->id, $this->nickname, $collection);
DB::flush();
throw new RedirectException();
}
@ -220,12 +212,18 @@ abstract class MetaCollectionController extends FeedController
];
}
/**
* @return ControllerResultType
*/
public function collectionsEntryViewNotesByNickname(Request $request, string $nickname, int $cid): array
{
$user = DB::findOneBy(LocalUser::class, ['nickname' => $nickname]);
return self::collectionsEntryViewNotesByActorId($request, $user->getId(), $cid);
}
/**
* @return ControllerResultType
*/
public function collectionsEntryViewNotesByActorId(Request $request, int $id, int $cid): array
{
$collection = $this->getCollectionBy($id, $cid);

View File

@ -4,6 +4,11 @@ declare(strict_types = 1);
namespace Component\Collection\Util\Controller;
class OrderedCollection extends Collection
/**
* @template T
*
* @extends Collection<T>
*/
abstract class OrderedCollection extends Collection
{
}

View File

@ -56,9 +56,9 @@ trait MetaCollectionTrait
/**
* create a collection owned by Actor $owner.
*
* @param Actor $owner The collection's owner
* @param array<string, mixed> $vars Page vars sent by AppendRightPanelBlock event
* @param string $name Collection's name
* @param Actor $owner The collection's owner
* @param array<string, mixed> $vars Page vars sent by AppendRightPanelBlock event
* @param string $name Collection's name
*/
abstract protected function createCollection(Actor $owner, array $vars, string $name): void;
/**
@ -82,6 +82,7 @@ trait MetaCollectionTrait
/**
* Check the route to determine whether the widget should be added
*
* @param array<string, mixed> $vars
*/
abstract protected function shouldAddToRightPanel(Actor $user, array $vars, Request $request): bool;
@ -91,7 +92,8 @@ trait MetaCollectionTrait
* @param Actor $owner Collection's owner
* @param null|array<string, mixed> $vars Page vars sent by AppendRightPanelBlock event
* @param bool $ids_only if true, the function must return only the primary key or each collections
* @return T[]|int[]
*
* @return int[]|T[]
*/
abstract protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array;
@ -101,7 +103,7 @@ trait MetaCollectionTrait
* the current item to, and another to create a new collection.
*
* @param array<string, mixed> $vars
* @param string[] $res
* @param string[] $res
*/
public function onAppendRightPanelBlock(Request $request, array $vars, array &$res): EventResult
{
@ -146,7 +148,7 @@ trait MetaCollectionTrait
if ($add_form->isSubmitted() && $add_form->isValid()) {
$selected = $add_form->getData()['collections'];
$removed = array_filter($already_selected, fn ($x) => !\in_array($x, $selected));
$added = array_filter($selected, fn ($x) => !\in_array($x, $already_selected));
$added = array_filter($selected, fn ($x) => !\in_array($x, $already_selected));
if (\count($removed) > 0) {
$this->removeItem($user, $vars, $removed, $collections);
}
@ -196,7 +198,7 @@ trait MetaCollectionTrait
}
/**
* @param string[]
* @param string[] $styles
*/
public function onEndShowStyles(array &$styles, string $route): EventResult
{

View File

@ -32,6 +32,9 @@ abstract class Parser
{
/**
* Merge $parts into $criteria_arr
*
* @param mixed[] $parts
* @param Criteria[] $criteria_arr
*/
private static function connectParts(array &$parts, array &$criteria_arr, string $last_op, mixed $eb, bool $force = false): void
{

View File

@ -46,6 +46,9 @@ use Component\Conversation\Entity\ConversationMute;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<\App\Entity\Note>
*/
class Conversation extends FeedController
{
/**
@ -55,7 +58,10 @@ class Conversation extends FeedController
*
* @throws \App\Util\Exception\ServerException
*
* @return array Array containing keys: 'notes' (all known notes in the given Conversation), 'should_format' (boolean, stating if onFormatNoteList events may or not format given notes), 'page_title' (used as the title header)
* @return ControllerResultType Array containing keys: 'notes' (all known
* notes in the given Conversation), 'should_format' (boolean, stating if
* onFormatNoteList events may or not format given notes), 'page_title'
* (used as the title header)
*/
public function showConversation(Request $request, int $conversation_id): array
{
@ -83,7 +89,7 @@ class Conversation extends FeedController
* @throws NoSuchNoteException
* @throws ServerException
*
* @return array
* @return ControllerResultType
*/
public function addReply(Request $request)
{
@ -103,7 +109,7 @@ class Conversation extends FeedController
* @throws \App\Util\Exception\RedirectException
* @throws \App\Util\Exception\ServerException
*
* @return array Array containing templating where the form is to be rendered, and the form itself
* @return ControllerResultType Array containing templating where the form is to be rendered, and the form itself
*/
public function muteConversation(Request $request, int $conversation_id)
{

View File

@ -95,11 +95,13 @@ class Conversation extends Component
* HTML rendering event that adds a reply link as a note
* action, if a user is logged in.
*
* @param \App\Entity\Note $note The Note being rendered
* @param array $actions Contains keys 'url' (linking 'conversation_reply_to'
* route), 'title' (used as title for aforementioned url),
* 'classes' (CSS styling classes used to visually inform the user of action context),
* 'id' (HTML markup id used to redirect user to this anchor upon performing the action)
* @param \App\Entity\Note $note The Note being rendered
* @param array{url: string, title: string, classes: string, id: string} $actions
* Contains keys 'url' (linking 'conversation_reply_to' route),
* 'title' (used as title for aforementioned url), 'classes' (CSS styling
* classes used to visually inform the user of action context), 'id' (HTML
* markup id used to redirect user to this anchor upon performing the
* action)
*
* @throws \App\Util\Exception\ServerException
*/
@ -138,8 +140,11 @@ class Conversation extends Component
/**
* Append on note information about user actions.
*
* @param array $vars Contains information related to Note currently being rendered
* @param array $result Contains keys 'actors', and 'action'. Needed to construct a string, stating who ($result['actors']), has already performed a reply ($result['action']), in the given Note (vars['note'])
* @param array<string, mixed> $vars Contains information related to Note currently being rendered
* @param array{actors: Actor[], action: string} $result
*cContains keys 'actors', and 'action'. Needed to construct a string,
* stating who ($result['actors']), has already performed a reply
* ($result['action']), in the given Note (vars['note'])
*/
public function onAppendCardNote(array $vars, array &$result): EventResult
{
@ -206,7 +211,7 @@ class Conversation extends Component
/**
* Posting event to add extra information to Component\Posting form data
*
* @param array $data Transport data to be filled with reply_to_id
* @param array{reply_to_id: int} $data Transport data to be filled with reply_to_id
*
* @throws \App\Util\Exception\ClientException
* @throws \App\Util\Exception\NoSuchNoteException
@ -224,6 +229,8 @@ class Conversation extends Component
/**
* Add minimal Note card to RightPanel template
*
* @param string[] $elements
*/
public function onPrependPostingForm(Request $request, array &$elements): EventResult
{
@ -250,8 +257,10 @@ class Conversation extends Component
/**
* Adds extra actions related to Conversation Component, that act upon/from the given Note.
*
* @param \App\Entity\Note $note Current Note being rendered
* @param array $actions Containing 'url' (Controller connected route), 'title' (used in anchor link containing the url), ?'classes' (CSS classes required for styling, if needed)
* @param \App\Entity\Note $note Current Note being rendered
* @param array{url: string, title: string, classes?: string} $actions Containing 'url' (Controller connected
* route), 'title' (used in anchor link containing the url), ?'classes' (CSS classes required for styling, if
* needed)
*
* @throws \App\Util\Exception\ServerException
*/

View File

@ -41,10 +41,15 @@ use App\Util\HTML\Heading;
use Component\Collection\Util\Controller\FeedController;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<\App\Entity\Note>
*/
class Feeds extends FeedController
{
/**
* The Planet feed represents every local post. Which is what this instance has to share with the universe.
*
* @return ControllerResultType
*/
public function public(Request $request): array
{
@ -60,6 +65,8 @@ class Feeds extends FeedController
/**
* The Home feed represents everything that concerns a certain actor (its subscriptions)
*
* @return ControllerResultType
*/
public function home(Request $request): array
{

View File

@ -244,6 +244,8 @@ class FreeNetwork extends Component
/**
* Add a link header for LRDD Discovery
*
* @param mixed $action
*/
public function onStartShowHTML($action): EventResult
{
@ -344,6 +346,7 @@ class FreeNetwork extends Component
* @param string $preMention Character(s) that signals a mention ('@', '!'...)
*
* @return array the matching URLs (without @ or acct:) and each respective position in the given string
*
* @example.com/mublog/user
*/
public static function extractUrlMentions(string $text, string $preMention = '@'): array
@ -375,6 +378,7 @@ class FreeNetwork extends Component
* @param $mentions
*
* @return bool hook return value
*
* @example.com/mublog/user
*/
public function onEndFindMentions(Actor $sender, string $text, array &$mentions): EventResult
@ -496,6 +500,9 @@ class FreeNetwork extends Component
return Event::next;
}
/**
* @param Actor[] $targets
*/
public static function notify(Actor $sender, Activity $activity, array $targets, ?string $reason = null): bool
{
foreach (self::$protocols as $protocol) {
@ -517,6 +524,9 @@ class FreeNetwork extends Component
/**
* Add fediverse: query expression
* // TODO: adding WebFinger would probably be nice
*
* @param mixed $note_expr
* @param mixed $actor_expr
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): EventResult
{

View File

@ -61,6 +61,8 @@ class Group extends Controller
*
* @throws RedirectException
* @throws ServerException
*
* @return ControllerResultType
*/
public function groupCreate(Request $request): array
{
@ -89,6 +91,8 @@ class Group extends Controller
* @throws NicknameTooLongException
* @throws NotFoundException
* @throws ServerException
*
* @return ControllerResultType
*/
public function groupSettings(Request $request, int $id): array
{

View File

@ -40,10 +40,15 @@ use Component\Subscription\Entity\ActorSubscription;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<\App\Entity\Note>
*/
class GroupFeed extends FeedController
{
/**
* @throws ServerException
*
* @return ControllerResultType
*/
public function groupView(Request $request, Actor $group): array
{
@ -96,6 +101,8 @@ class GroupFeed extends FeedController
/**
* @throws ClientException
* @throws ServerException
*
* @return ControllerResultType
*/
public function groupViewId(Request $request, int $id): array
{
@ -119,6 +126,8 @@ class GroupFeed extends FeedController
*
* @throws ClientException
* @throws ServerException
*
* @return ControllerResultType
*/
public function groupViewNickname(Request $request, string $nickname): array
{

View File

@ -50,6 +50,8 @@ class Group extends Component
/**
* Enqueues a notification for an Actor (such as person or group) which means
* it shows up in their home feed and such.
*
* @param Actor[] $targets
*/
public function onNewNotificationStart(Actor $sender, Activity $activity, array $targets = [], ?string $reason = null): EventResult
{
@ -70,6 +72,9 @@ class Group extends Component
/**
* Add an <a href=group_actor_settings> to the profile card for groups, if the current actor can access them
*
* @param array<string, mixed> $vars
* @param string[] $res
*/
public function onAppendCardProfile(array $vars, array &$res): EventResult
{
@ -109,6 +114,9 @@ class Group extends Component
return null;
}
/**
* @param Actor[] $targets
*/
public function onPostingFillTargetChoices(Request $request, Actor $actor, array &$targets): EventResult
{
$group = $this->getGroupFromContext($request);

View File

@ -70,7 +70,7 @@ class Language extends Controller
['actor_id' => $user->getId()],
);
$new_langs = array_udiff($selected_langs, $existing_langs, fn ($l, $r) => $l->getId() <=> $r->getId());
$new_langs = array_udiff($selected_langs, $existing_langs, fn ($l, $r) => $l->getId() <=> $r->getId());
$removing_langs = array_udiff($existing_langs, $selected_langs, fn ($l, $r) => $l->getId() <=> $r->getId());
foreach ($new_langs as $l) {
DB::persist(ActorLanguage::create(['actor_id' => $user->getId(), 'language_id' => $l->getId(), 'ordering' => 0]));
@ -100,6 +100,8 @@ class Language extends Controller
* @throws NoLoggedInUser
* @throws RedirectException
* @throws ServerException
*
* @return ControllerResultType
*/
public function sortLanguages(Request $request): array
{

View File

@ -119,6 +119,9 @@ class ActorLanguage extends Entity
) ?: [Language::getByLocale(Common::config('site', 'language'))];
}
/**
* @return int[]
*/
public static function getActorRelatedLanguagesIds(Actor $actor): array
{
return Cache::getList(

View File

@ -134,6 +134,9 @@ class Language extends Entity
return self::getById($note->getLanguageId());
}
/**
* @return array<string, string>
*/
public static function getLanguageChoices(): array
{
$langs = Cache::getHashMap(
@ -144,6 +147,8 @@ class Language extends Entity
return array_merge(...F\map(array_values($langs), fn ($l) => $l->toChoiceFormat()));
}
/**
* @return array<string, string> */
public function toChoiceFormat(): array
{
return [_m($this->getLongDisplay()) => $this->getLocale()];
@ -152,6 +157,8 @@ class Language extends Entity
/**
* Get all the available languages as well as the languages $actor
* prefers and are appropriate for posting in/to $context_actor
*
* @return array{array<string, string>, array<string, string>}
*/
public static function getSortedLanguageChoices(?Actor $actor, ?Actor $context_actor, ?bool $use_short_display): array
{

View File

@ -45,6 +45,9 @@ class Language extends Component
return Event::next;
}
/**
* @param Note[] $notes
*/
public function onFilterNoteList(?Actor $actor, array &$notes, Request $request): EventResult
{
if (\is_null($actor)) {
@ -60,6 +63,9 @@ class Language extends Component
/**
* Populate $note_expr or $actor_expr with an expression to match a language
*
* @param mixed $note_expr
* @param mixed $actor_expr
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): EventResult
{

View File

@ -43,6 +43,8 @@ class LeftPanel extends Component
}
/**
* @param array<string, string> $route_params
*
* @throws \App\Util\Exception\DuplicateFoundException
* @throws \App\Util\Exception\ServerException
* @throws ClientException

View File

@ -84,6 +84,8 @@ class NoteToLink extends Entity
* Create an instance of NoteToLink or fill in the
* properties of $obj with the associative array $args. Doesn't
* persist the result
*
* @param (array{link_id: int, note_id: int} & array<string, mixed>) $args
*/
public static function create(array $args, bool $_delegated_call = false): static
{

View File

@ -54,6 +54,8 @@ class Link extends Component
/**
* Extract URLs from $content and create the appropriate Link and NoteToLink entities
*
* @param array{ignoreLinks?: string[]} $process_note_content_extra_args
*/
public function onProcessNoteContent(Note $note, string $content, string $content_type, array $process_note_content_extra_args = []): EventResult
{
@ -150,7 +152,12 @@ class Link extends Component
public const URL_SCHEME_NO_DOMAIN = 4;
public const URL_SCHEME_COLON_COORDINATES = 8;
public function URLSchemes($filter = null)
/**
* @param self::URL_SCHEME_COLON_COORDINATES|self::URL_SCHEME_COLON_DOUBLE_SLASH|self::URL_SCHEME_NO_DOMAIN|self::URL_SCHEME_SINGLE_COLON $filter
*
* @return string[]
*/
public function URLSchemes(?int $filter = null): array
{
// TODO: move these to config
$schemes = [
@ -197,6 +204,7 @@ class Link extends Component
* Intermediate callback for `replaceURLs()`, which helps resolve some
* ambiguous link forms before passing on to the final callback.
*
* @param string[] $matches
* @param callable(string $text): string $callback: return replacement text
*/
private function callbackHelper(array $matches, callable $callback): string

View File

@ -44,6 +44,8 @@ class Feed extends Controller
{
/**
* Everything with attention to current user
*
* @return ControllerResultType
*/
public function notifications(Request $request): array
{

View File

@ -117,7 +117,7 @@ class Notification extends Entity
/**
* Pull the complete list of known activity context notifications for this activity.
*
* @return array of integer actor ids (also group profiles)
* @return int[] actor ids (also group profiles)
*/
public static function getNotificationTargetIdsByActivity(int|Activity $activity_id): array
{
@ -129,11 +129,17 @@ class Notification extends Entity
return $targets;
}
/**
* @return int[]
*/
public function getNotificationTargetsByActivity(int|Activity $activity_id): array
{
return DB::findBy(Actor::class, ['id' => $this->getNotificationTargetIdsByActivity($activity_id)]);
}
/**
* @return int[]
*/
public static function getAllActivitiesTargetedAtActor(Actor $actor): array
{
return DB::dql(<<<'EOF'

View File

@ -71,10 +71,10 @@ class Notification extends Component
* Example of $targets:
* [42, $actor_alice, $actor_bob] // Avoid repeating actors or ids
*
* @param Actor $sender The one responsible for this activity, take care not to include it in targets
* @param Activity $activity The activity responsible for the object being given to known to targets
* @param array $targets Attentions, Mentions, any other source. Should never be empty, you usually want to register an attention to every $sender->getSubscribers()
* @param null|string $reason An optional reason explaining why this notification exists
* @param Actor $sender The one responsible for this activity, take care not to include it in targets
* @param Activity $activity The activity responsible for the object being given to known to targets
* @param non-empty-array<Actor|int> $targets Attentions, Mentions, any other source. Should never be empty, you usually want to register an attention to every $sender->getSubscribers()
* @param null|string $reason An optional reason explaining why this notification exists
*/
public function onNewNotification(Actor $sender, Activity $activity, array $targets, ?string $reason = null): EventResult
{
@ -103,12 +103,19 @@ class Notification extends Component
return Event::next;
}
/**
* @param mixed[] $retry_args
*/
public function onQueueNotificationLocal(Actor $sender, Activity $activity, Actor $target, ?string $reason, array &$retry_args): EventResult
{
// TODO: use https://symfony.com/doc/current/notifier.html
return Event::stop;
}
/**
* @param Actor[] $targets
* @param mixed[] $retry_args
*/
public function onQueueNotificationRemote(Actor $sender, Activity $activity, array $targets, ?string $reason, array &$retry_args): EventResult
{
if (FreeNetwork::notify($sender, $activity, $targets, $reason)) {
@ -122,7 +129,9 @@ class Notification extends Component
* Bring given Activity to Targets' knowledge.
* This will flush a Notification to DB.
*
* @return true if successful, false otherwise
* @param Actor[] $targets
*
* @return bool true if successful, false otherwise
*/
public static function notify(Actor $sender, Activity $activity, array $targets, ?string $reason = null): bool
{
@ -134,7 +143,7 @@ class Notification extends Component
continue;
}
if (Event::handle('NewNotificationShould', [$activity, $target]) === Event::next) {
if ($sender->getId() === $target->getId()
if ($sender->getId() === $target->getId()
|| $activity->getActorId() === $target->getId()) {
// The target already knows about this, no need to bother with a notification
continue;

View File

@ -34,11 +34,16 @@ use App\Util\HTML\Heading;
use Component\Collection\Util\Controller\FeedController;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<\App\Entity\Note>
*/
class PersonFeed extends FeedController
{
/**
* @throws ClientException
* @throws ServerException
*
* @return ControllerResultType
*/
public function personViewId(Request $request, int $id): array
{
@ -62,6 +67,8 @@ class PersonFeed extends FeedController
*
* @throws ClientException
* @throws ServerException
*
* @return ControllerResultType
*/
public function personViewNickname(Request $request, string $nickname): array
{
@ -73,6 +80,9 @@ class PersonFeed extends FeedController
return $this->personView($request, $person);
}
/**
* @return ControllerResultType
*/
public function personView(Request $request, Actor $person): array
{
return [

View File

@ -88,6 +88,8 @@ class PersonSettings extends Controller
* @throws NoLoggedInUser
* @throws RedirectException
* @throws ServerException
*
* @return ControllerResultType
*/
public function allSettings(Request $request, LanguageController $language): array
{
@ -205,6 +207,8 @@ class PersonSettings extends Controller
* @throws \Doctrine\DBAL\Exception
* @throws NoLoggedInUser
* @throws ServerException
*
* @return ControllerResultType[]
*/
private static function notifications(Request $request): array
{
@ -251,7 +255,7 @@ class PersonSettings extends Controller
// @codeCoverageIgnoreStart
Log::critical("Structure of table user_notification_prefs changed in a way not accounted to in notification settings ({$name}): " . $type_str);
throw new ServerException(_m('Internal server error'));
// @codeCoverageIgnoreEnd
// @codeCoverageIgnoreEnd
}
}

View File

@ -44,11 +44,13 @@ use App\Util\Exception\ServerException;
use App\Util\Formatting;
use App\Util\HTML;
use Component\Attachment\Entity\ActorToAttachment;
use Component\Attachment\Entity\Attachment;
use Component\Attachment\Entity\AttachmentToNote;
use Component\Conversation\Conversation;
use Component\Language\Entity\Language;
use Component\Notification\Entity\Attention;
use EventResult;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\Request;
@ -66,6 +68,8 @@ class Posting extends Component
* HTML render event handler responsible for adding and handling
* the result of adding the note submission form, only if a user is logged in
*
* @param array{post_form?: FormInterface} $res
*
* @throws BugFoundException
* @throws ClientException
* @throws DuplicateFoundException
@ -84,9 +88,25 @@ class Posting extends Component
}
/**
* @param Actor $actor The Actor responsible for the creation of this Note
* @param null|string $content The raw text content
* @param string $content_type Indicating one of the various supported content format (Plain Text, Markdown, LaTeX...)
* @param null|string $locale Note's written text language, set by the default Actor language or upon filling
* @param null|VisibilityScope $scope The visibility of this Note
* @param Actor[]|int[] $attentions Actor|int[]: In Group/To Person or Bot, registers an attention between note and target
* @param null|int|Note $reply_to The soon-to-be Note parent's id, if it's a Reply itself
* @param UploadedFile[] $attachments UploadedFile[] to be stored as GSFiles associated to this note
* @param array<array{Attachment, string}> $processed_attachments Array of [Attachment, Attachment's name][] to be associated to this $actor and Note
* @param array{note?: Note, content?: string, content_type?: string, extra_args?: array<string, mixed>} $process_note_content_extra_args Extra arguments for the event ProcessNoteContent
* @param bool $flush_and_notify True if the newly created Note activity should be passed on as a Notification
* @param null|string $rendered The Note's content post RenderNoteContent event, which sanitizes and processes the raw content sent
* @param string $source The source of this Note
*
* @throws ClientException
* @throws DuplicateFoundException
* @throws ServerException
*
* @return array{\App\Entity\Activity, \App\Entity\Note, array<int, \App\Entity\Actor>}
*/
public static function storeLocalArticle(
Actor $actor,
@ -144,25 +164,25 @@ class Posting extends Component
* $actor_id, possibly as a reply to note $reply_to and with flag
* $is_local. Sanitizes $content and $attachments
*
* @param Actor $actor The Actor responsible for the creation of this Note
* @param null|string $content The raw text content
* @param string $content_type Indicating one of the various supported content format (Plain Text, Markdown, LaTeX...)
* @param null|string $locale Note's written text language, set by the default Actor language or upon filling
* @param null|VisibilityScope $scope The visibility of this Note
* @param array $attentions Actor|int[]: In Group/To Person or Bot, registers an attention between note and target
* @param null|int|Note $reply_to The soon-to-be Note parent's id, if it's a Reply itself
* @param array $attachments UploadedFile[] to be stored as GSFiles associated to this note
* @param array $processed_attachments Array of [Attachment, Attachment's name][] to be associated to this $actor and Note
* @param array $process_note_content_extra_args Extra arguments for the event ProcessNoteContent
* @param bool $flush_and_notify True if the newly created Note activity should be passed on as a Notification
* @param null|string $rendered The Note's content post RenderNoteContent event, which sanitizes and processes the raw content sent
* @param string $source The source of this Note
* @param Actor $actor The Actor responsible for the creation of this Note
* @param null|string $content The raw text content
* @param string $content_type Indicating one of the various supported content format (Plain Text, Markdown, LaTeX...)
* @param null|string $locale Note's written text language, set by the default Actor language or upon filling
* @param null|VisibilityScope $scope The visibility of this Note
* @param Actor[]|int[] $attentions Actor|int[]: In Group/To Person or Bot, registers an attention between note and targte
* @param null|int|Note $reply_to The soon-to-be Note parent's id, if it's a Reply itself
* @param UploadedFile[] $attachments UploadedFile[] to be stored as GSFiles associated to this note
* @param array<array{Attachment, string}> $processed_attachments Array of [Attachment, Attachment's name][] to be associated to this $actor and Note
* @param array{note?: Note, content?: string, content_type?: string, extra_args?: array<string, mixed>} $process_note_content_extra_args Extra arguments for the event ProcessNoteContent
* @param bool $flush_and_notify True if the newly created Note activity should be passed on as a Notification
* @param null|string $rendered The Note's content post RenderNoteContent event, which sanitizes and processes the raw content sent
* @param string $source The source of this Note
*
* @throws ClientException
* @throws DuplicateFoundException
* @throws ServerException
*
* @return array [Activity, Note, Effective Attentions]
* @return array{\App\Entity\Activity, \App\Entity\Note, array<int, \App\Entity\Actor>}
*/
public static function storeLocalNote(
Actor $actor,
@ -302,6 +322,9 @@ class Posting extends Component
return [$activity, $note, $effective_attentions];
}
/**
* @param array<int, \App\Entity\Actor> $mentions
*/
public function onRenderNoteContent(string $content, string $content_type, ?string &$rendered, Actor $author, ?string $language = null, array &$mentions = []): EventResult
{
switch ($content_type) {

View File

@ -38,12 +38,17 @@ use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<\App\Entity\Note>
*/
class Search extends FeedController
{
/**
* Handle a search query
*
* @return ControllerResultType
*/
public function handle(Request $request)
public function handle(Request $request): array
{
$actor = Common::actor();
$language = !\is_null($actor) ? $actor->getTopLanguage()->getLocale() : null;

View File

@ -134,6 +134,8 @@ class Search extends Component
/**
* Add the search form to the site header
*
* @param string[] $elements
*
* @throws RedirectException
*/
public function onPrependRightPanelBlock(Request $request, array &$elements): EventResult
@ -145,7 +147,7 @@ class Search extends Component
/**
* Output our dedicated stylesheet
*
* @param array $styles stylesheets path
* @param string[] $styles stylesheets path
*/
public function onEndShowStyles(array &$styles, string $route): EventResult
{

View File

@ -45,6 +45,8 @@ class Subscribers extends CircleController
{
/**
* @throws ServerException
*
* @return ControllerResultType
*/
public function subscribersByActor(Request $request, Actor $actor): array
{
@ -61,6 +63,8 @@ class Subscribers extends CircleController
/**
* @throws ClientException
* @throws ServerException
*
* @return ControllerResultType
*/
public function subscribersByActorId(Request $request, int $id): array
{
@ -78,6 +82,8 @@ class Subscribers extends CircleController
* @throws \App\Util\Exception\ServerException
* @throws ClientException
* @throws RedirectException
*
* @return ControllerResultType
*/
public function subscribersAdd(Request $request, int $object_id): array
{
@ -126,6 +132,8 @@ class Subscribers extends CircleController
* @throws \App\Util\Exception\ServerException
* @throws ClientException
* @throws RedirectException
*
* @return ControllerResultType
*/
public function subscribersRemove(Request $request, int $object_id): array
{

View File

@ -38,6 +38,8 @@ class Subscriptions extends CircleController
/**
* @throws ClientException
* @throws ServerException
*
* @return ControllerResultType
*/
public function subscriptionsByActorId(Request $request, int $id): array
{
@ -48,7 +50,10 @@ class Subscriptions extends CircleController
return $this->subscriptionsByActor($request, $actor);
}
public function subscriptionsByActor(Request $request, Actor $actor)
/**
* @return ControllerResultType
*/
public function subscriptionsByActor(Request $request, Actor $actor): array
{
return [
'_template' => 'collection/actors.html.twig',

View File

@ -58,6 +58,8 @@ class Subscription extends Component
*
* @param Actor|int|LocalUser $subject The Actor who subscribed or unsubscribed
* @param Actor|int|LocalUser $object The Actor who was subscribed or unsubscribed from
*
* @return array{bool, bool}
*/
public static function refreshSubscriptionCount(int|Actor|LocalUser $subject, int|Actor|LocalUser $object): array
{
@ -177,9 +179,10 @@ class Subscription extends Component
* In the case of ``\App\Component\Subscription``, the action added allows a **LocalUser** to **subscribe** or
* **unsubscribe** a given **Actor**.
*
* @param Actor $object The Actor on which the action is to be performed
* @param array $actions An array containing all actions added to the
* current profile, this event adds an action to it
* @param Actor $object The Actor on which the action is to be performed
* @param array<array{url: string, title: string, classes: string, id: string}> $actions
* An array containing all actions added to the
* current profile, this event adds an action to it
*
* @throws DuplicateFoundException
* @throws NotFoundException

View File

@ -15,7 +15,12 @@ class Tag extends Controller
// TODO: Use Feed::query
// TODO: If ?canonical=something, respect
// TODO: Allow to set locale of tag being selected
private function process(null|string|array $tag_single_or_multi, string $key, string $query, string $template, bool $include_locale = false)
/**
* @param (null|string|string[]) $tag_single_or_multi
*
* @return ControllerResultType
*/
private function process(null|string|array $tag_single_or_multi, string $key, string $query, string $template, bool $include_locale = false): array
{
$actor = Common::actor();
$page = $this->int('page') ?: 1;
@ -46,7 +51,10 @@ class Tag extends Controller
];
}
public function single_note_tag(string $tag)
/**
* @return ControllerResultType
*/
public function single_note_tag(string $tag): array
{
return $this->process(
tag_single_or_multi: $tag,
@ -57,7 +65,10 @@ class Tag extends Controller
);
}
public function multi_note_tags(string $tags)
/**
* @return ControllerResultType
*/
public function multi_note_tags(string $tags): array
{
return $this->process(
tag_single_or_multi: explode(',', $tags),

View File

@ -134,6 +134,9 @@ class NoteTag extends Entity
return "note-tags-{$note_id}";
}
/**
* @return NoteTag[]
*/
public static function getByNoteId(int $note_id): array
{
return Cache::getList(self::cacheKey($note_id), fn () => DB::dql('SELECT nt FROM note_tag AS nt JOIN note AS n WITH n.id = nt.note_id WHERE n.id = :id', ['id' => $note_id]));

View File

@ -119,6 +119,8 @@ class NoteTagBlock extends Entity
/**
* Check whether $note_tag is considered blocked by one of
* $note_tag_blocks
*
* @param NoteTagBlock[] $note_tag_blocks
*/
public static function checkBlocksNoteTag(NoteTag $note_tag, array $note_tag_blocks): bool
{

View File

@ -61,13 +61,16 @@ class Tag extends Component
public const TAG_REGEX = '/(^|\\s)(#[\\pL\\pN_\\-]{1,64})/u'; // Brion Vibber 2011-02-23 v2:classes/Notice.php:367 function saveTags
public const TAG_SLUG_REGEX = '[A-Za-z0-9]{1,64}';
public function onAddRoute($r): EventResult
public function onAddRoute(Router $r): EventResult
{
$r->connect('single_note_tag', '/note-tag/{tag<' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'single_note_tag']);
$r->connect('multi_note_tags', '/note-tags/{tags<(' . self::TAG_SLUG_REGEX . ',)+' . self::TAG_SLUG_REGEX . '>}', [Controller\Tag::class, 'multi_note_tags']);
return Event::next;
}
/**
* @param array{tag_use_canonical?: bool} $extra_args
*/
public static function maybeCreateTag(string $tag, int $note_id, ?int $lang_id, array $extra_args = []): ?NoteTag
{
if (!self::validate($tag)) {
@ -118,6 +121,8 @@ class Tag extends Component
/**
* Process note by extracting any tags present
*
* @param array{TagProcessed?: bool} $extra_args
*/
public function onProcessNoteContent(Note $note, string $content, string $content_type, array $extra_args): EventResult
{
@ -213,6 +218,9 @@ class Tag extends Component
* Populate $note_expr with an expression to match a tag, if the term looks like a tag
*
* $term /^(note|tag|people|actor)/ means we want to match only either a note or an actor
*
* @param mixed $note_expr
* @param mixed $actor_expr
*/
public function onCollectionQueryCreateExpression(ExpressionBuilder $eb, string $term, ?string $locale, ?Actor $actor, &$note_expr, &$actor_expr): EventResult
{
@ -264,12 +272,19 @@ class Tag extends Component
return Event::next;
}
/**
* @param array{string, class-string, array<string, mixed>} $form_params
*/
public function onPostingAddFormEntries(Request $request, Actor $actor, array &$form_params): EventResult
{
$form_params[] = ['tag_use_canonical', CheckboxType::class, ['required' => false, 'data' => true, 'label' => _m('Make note tags canonical'), 'help' => _m('Canonical tags will be treated as a version of an existing tag with the same root/stem (e.g. \'#great_tag\' will be considered as a version of \'#great\', if it already exists)')]];
return Event::next;
}
/**
* @param array{tag_use_canonical?: bool} $data
* @param array{tag_use_canonical?: bool} $extra_args
*/
public function onAddExtraArgsToNoteContent(Request $request, Actor $actor, array $data, array &$extra_args): EventResult
{
if (!isset($data['tag_use_canonical'])) {

View File

@ -1,5 +1,7 @@
parameters:
level: 5
level: 6
tmpDir: /var/www/social/var/cache/phpstan
inferPrivatePropertyTypeFromConstructor: true
bootstrapFiles:
- config/bootstrap.php
paths:
@ -16,10 +18,11 @@ parameters:
App\Core\Log:
- unexpected_exception
typeAliases:
ControllerResultType: 'array{_template: string} & array<string, mixed>'
ControllerResultType: '(array{_template: string} | array{_redirect: string}) & array<string, mixed>'
CacheKeysType: 'array<string, string>'
SettingsTabsType: 'array<array{title: string, desc: string, id: string, controller: ControllerResultType}>'
OrderByType: "'ASC' | 'DESC' | 'asc' | 'desc'"
ModuleVersionType: 'array{name: string, version: string, author: string, rawdescription: string}'
ignoreErrors:
-
@ -41,10 +44,41 @@ parameters:
paths:
- *
# -
# message: '/has no return typehint specified/'
# paths:
# - tests/*
-
message: '/::onCollectionQueryCreateExpression\(\) has parameter \$(actor|note)_expr with no type specified\./'
paths:
- *
-
message: '/::schemaDef\(\) return type has no value type specified in iterable type array\./'
paths:
- *
-
message: '/has no return type specified\./'
paths:
- *
-
message: '/with no value type specified in iterable type (array|iterable)\.|type has no value type specified in iterable type (array|iterable)\./'
paths:
- *
-
message: '/never returns array{_redirect: string} so it can be removed from the return type\./'
paths:
- *
-
message: '/but returns array<string, array<int, mixed>\|string>\./'
paths:
- plugins/AttachmentCollections/Controller/AttachmentCollections.php
- plugins/Bundles/Controller/BundleCollection.php
-
message: '/has parameter \$.+ with no type specified./'
paths:
- tests/*
services:
-

View File

@ -49,9 +49,14 @@ use Symfony\Component\HttpFoundation\Request;
class AttachmentCollections extends Plugin
{
/** @phpstan-use MetaCollectionTrait<AttachmentCollection> */
use MetaCollectionTrait;
protected const SLUG = 'collection';
protected const PLURAL_SLUG = 'collections';
/**
* @param array<string, mixed> $vars
*/
protected function createCollection(Actor $owner, array $vars, string $name): void
{
$col = AttachmentCollection::create([
@ -65,6 +70,12 @@ class AttachmentCollections extends Plugin
'attachment_collection_id' => $col->getId(),
]));
}
/**
* @param array<string, mixed> $vars
* @param int[] $items
* @param array<string, mixed> $collections
*/
protected function removeItem(Actor $owner, array $vars, array $items, array $collections): bool
{
return DB::dql(<<<'EOF'
@ -83,6 +94,11 @@ class AttachmentCollections extends Plugin
]);
}
/**
* @param array<string, mixed> $vars
* @param int[] $items
* @param array<string, mixed> $collections
*/
protected function addItem(Actor $owner, array $vars, array $items, array $collections): void
{
foreach ($items as $id) {
@ -97,14 +113,18 @@ class AttachmentCollections extends Plugin
}
}
protected function shouldAddToRightPanel(Actor $user, $vars, Request $request): bool
/**
* @param array<string, mixed> $vars
*/
protected function shouldAddToRightPanel(Actor $user, array $vars, Request $request): bool
{
return $vars['path'] === 'note_attachment_show';
}
/**
* @param array<string, mixed> $vars
* @return int[]
*
* @return ($ids_only is true ? int[] : AttachmentCollection[])
*/
protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array
{

View File

@ -28,6 +28,9 @@ use App\Core\Router;
use Component\Collection\Util\Controller\MetaCollectionController;
use Plugin\AttachmentCollections\Entity\AttachmentCollection;
/**
* @extends MetaCollectionController<AttachmentCollection>
*/
class AttachmentCollections extends MetaCollectionController
{
public function createCollection(int $owner_id, string $name): bool
@ -36,6 +39,8 @@ class AttachmentCollections extends MetaCollectionController
'name' => $name,
'actor_id' => $owner_id,
]));
return true;
}
public function getCollectionUrl(int $owner_id, ?string $owner_nickname, int $collection_id): string
@ -51,7 +56,12 @@ class AttachmentCollections extends MetaCollectionController
['nickname' => $owner_nickname, 'cid' => $collection_id],
);
}
public function getCollectionItems(int $owner_id, $collection_id): array
/**
* FIXME return value not consistent with base class
*/
// @phpstan-disable-next-line
public function getCollectionItems(int $owner_id, int $collection_id): array
{
[$attachs, $notes] = DB::dql(
<<<'EOF'
@ -64,28 +74,30 @@ class AttachmentCollections extends MetaCollectionController
EOF,
['cid' => $collection_id],
);
return [
'_template' => 'AttachmentCollections/collection_entry_view.html.twig',
'attachments' => array_values($attachs),
'bare_notes' => array_values($notes),
];
return ['_template' => 'AttachmentCollections/collection_entry_view.html.twig', 'attachments' => array_values($attachs), 'bare_notes' => array_values($notes)];
}
/**
* @return AttachmentCollection[]
*/
public function getCollectionsByActorId(int $owner_id): array
{
return DB::findBy(AttachmentCollection::class, ['actor_id' => $owner_id], order_by: ['id' => 'desc']);
}
public function getCollectionBy(int $owner_id, int $collection_id): AttachmentCollection
{
return DB::findOneBy(AttachmentCollection::class, ['id' => $collection_id]);
}
public function setCollectionName(int $actor_id, string $actor_nickname, AttachmentCollection $collection, string $name)
public function setCollectionName(int $actor_id, string $actor_nickname, AttachmentCollection $collection, string $name): void
{
$collection->setName($name);
DB::persist($collection);
}
public function removeCollection(int $actor_id, string $actor_nickname, AttachmentCollection $collection)
public function removeCollection(int $actor_id, string $actor_nickname, AttachmentCollection $collection): void
{
DB::remove($collection);
}

View File

@ -35,7 +35,7 @@ class AttachmentShowRelated extends Plugin
{
/**
* @param array<string, mixed> $vars
* @param string[] $res
* @param string[] $res
*/
public function onAppendRightPanelBlock(Request $request, array $vars, array &$res): EventResult
{
@ -55,7 +55,7 @@ class AttachmentShowRelated extends Plugin
/**
* Output our dedicated stylesheet
*
* @param array $styles stylesheets path
* @param string[] $styles stylesheets path
*/
public function onEndShowStyles(array &$styles, string $path): EventResult
{

View File

@ -54,6 +54,9 @@ class AudioEncoder extends Plugin
return GSFile::mimetypeMajor($mimetype) === 'audio';
}
/**
* @param array<string, callable> $event_map
*/
public function onFileMetaAvailable(array &$event_map, string $mimetype): EventResult
{
if (!self::shouldHandle($mimetype)) {
@ -90,6 +93,9 @@ class AudioEncoder extends Plugin
/**
* Generates the view for attachments of type Video
*
* @param (array{attachment: \Component\Attachment\Entity\Attachment, note: \App\Entity\Note, title: string} & array<string, mixed>) $vars
* @param array<string> $res
*/
public function onViewAttachment(array $vars, array &$res): EventResult
{
@ -109,6 +115,8 @@ class AudioEncoder extends Plugin
}
/**
* @param ModuleVersionType[] $versions
*
* @throws ServerException
*/
public function onPluginVersion(array &$versions): EventResult

View File

@ -39,6 +39,10 @@ class Blog extends Plugin
return Event::next;
}
/**
* @param (array{actor: \App\Entity\Actor} & array<string, mixed>) $vars
* @param array<string> $res
*/
public function onAppendCardProfile(array $vars, array &$res): EventResult
{
$actor = Common::actor();

View File

@ -32,6 +32,9 @@ use Symfony\Component\HttpFoundation\Request;
class Bundles extends Plugin
{
/**
* @phpstan-use MetaCollectionTrait<BundleCollection>
*/
use MetaCollectionTrait;
protected const SLUG = 'bundle';
protected const PLURAL_SLUG = 'bundles';
@ -54,7 +57,8 @@ class Bundles extends Plugin
/**
* @param array<string, mixed> $vars
* @param array<int> $items
* @param array<int> $items
* @param array<mixed> $collections
*/
protected function removeItem(Actor $owner, array $vars, array $items, array $collections): bool
{
@ -75,8 +79,8 @@ class Bundles extends Plugin
/**
* @param array<string, mixed> $vars
* @param array<int> $items
* @param array<int> $collections
* @param array<int> $items
* @param array<int> $collections
*/
protected function addItem(Actor $owner, array $vars, array $items, array $collections): void
{
@ -101,8 +105,11 @@ class Bundles extends Plugin
}
/**
* @param array<string, mixed> $vars
* @retrun int[]
* FIXME incompatible return type
*
* @param null|array<string, mixed> $vars
*
* @return BundleCollection[]|int[]
*/
protected function getCollectionsBy(Actor $owner, ?array $vars = null, bool $ids_only = false): array
{

View File

@ -26,9 +26,12 @@ namespace Plugin\Bundles\Controller;
use App\Core\DB;
use App\Core\Router;
use Component\Collection\Util\Controller\MetaCollectionController;
use Plugin\Bundles\Entity\BundleCollection;
use Plugin\Bundles\Entity\BundleCollection as BundleCollectionEntity;
class BundleCollections extends MetaCollectionController
/**
* @extends MetaCollectionController<BundleCollectionEntity>
*/
class BundleCollection extends MetaCollectionController
{
public function getCollectionUrl(int $owner_id, ?string $owner_nickname, int $collection_id): string
{
@ -44,7 +47,8 @@ class BundleCollections extends MetaCollectionController
);
}
public function getCollectionItems(int $owner_id, $collection_id): array
// FIXME
public function getCollectionItems(int $owner_id, int $collection_id): array
{
[$notes] = DB::dql(
<<<'EOF'
@ -65,17 +69,17 @@ class BundleCollections extends MetaCollectionController
public function getCollectionsByActorId(int $owner_id): array
{
return DB::findBy(BundleCollection::class, ['actor_id' => $owner_id], order_by: ['id' => 'desc']);
return DB::findBy(BundleCollectionEntity::class, ['actor_id' => $owner_id], order_by: ['id' => 'desc']);
}
public function getCollectionBy(int $owner_id, int $collection_id): BundleCollection
public function getCollectionBy(int $owner_id, int $collection_id): BundleCollectionEntity
{
return DB::findOneBy(BundleCollection::class, ['id' => $collection_id]);
return DB::findOneBy(BundleCollectionEntity::class, ['id' => $collection_id]);
}
public function createCollection(int $owner_id, string $name): bool
{
DB::persist(BundleCollection::create([
DB::persist(BundleCollectionEntity::create([
'name' => $name,
'actor_id' => $owner_id,
]));

View File

@ -58,7 +58,7 @@ class Cover
* @throws ClientException Invalid form
* @throws ServerException Invalid file type
*
* @return array template
* @return ControllerResultType
*/
public static function coverSettings(Request $request): array
{
@ -113,7 +113,7 @@ class Cover
// Only delete files if the commit went through
if ($old_file != null) {
foreach ($old_file as $f) {
@unlink($f);
@unlink($f->getPath());
}
}
throw new RedirectException();
@ -131,7 +131,7 @@ class Cover
DB::remove($cover);
DB::flush();
foreach ($old_file as $f) {
@unlink($f);
@unlink($f->getPath());
}
throw new RedirectException();
}

View File

@ -90,7 +90,7 @@ class Cover extends Plugin
/**
* Output our dedicated stylesheet
*
* @param array $styles stylesheets path
* @param string[] $styles stylesheets path
*/
public function onStartShowStyles(array &$styles): EventResult
{

View File

@ -117,7 +117,7 @@ class Cover extends Entity
/**
* Delete this cover and the corresponding attachment and thumbnails, which this owns
*
* @return array attachments deleted (if delete_attachments_now is true)
* @return Attachment[] attachments deleted (if delete_attachments_now is true)
*/
public function delete(bool $flush = false, bool $delete_attachments_now = false, bool $cascading = false): array
{

View File

@ -150,6 +150,8 @@ class DeleteNote extends NoteHandlerPlugin
* @throws \App\Util\Exception\DuplicateFoundException
* @throws \App\Util\Exception\NotFoundException
* @throws \App\Util\Exception\ServerException
*
* @params string[] $actions
*/
public function onAddExtraNoteActions(Request $request, Note $note, array &$actions): EventResult
{

View File

@ -40,7 +40,7 @@ use EventResult;
class EmailNotifications extends Plugin
{
public function onAddNotificationTransport(&$form_defs): EventResult
public function onAddNotificationTransport(array &$form_defs): EventResult
{
$form_defs['Email'] = $form_defs['placeholder'];
$form_defs['Email'][] = $form_defs['placeholder']['save']('Email', 'save_email');

View File

@ -216,7 +216,7 @@ class OEmbed extends Controller
/**
* Placeholder
*/
public function init_document($type)
public function init_document(string $type): void
{
throw new NotImplementedException;
// switch ($type) {
@ -243,7 +243,7 @@ class OEmbed extends Controller
/**
* Placeholder
*/
public function end_document($type)
public function end_document(string $type): void
{
throw new NotImplementedException;
// switch ($type) {

View File

@ -26,6 +26,7 @@ namespace Plugin\Favourite\Controller;
use App\Core\DB;
use App\Core\Event;
use App\Core\Form;
use function App\Core\I18n\_m;
use App\Core\Log;
use App\Core\Router;
use App\Entity\Activity;
@ -41,10 +42,12 @@ use App\Util\Exception\RedirectException;
use App\Util\Exception\ServerException;
use Component\Collection\Util\Controller\FeedController;
use Component\Notification\Entity\Attention;
use function App\Core\I18n\_m;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<Note>
*/
class Favourite extends FeedController
{
/**

View File

@ -44,7 +44,8 @@ class LatexNotes extends Plugin
$types['LaTeX'] = 'application/x-latex';
return Event::next;
}
public function onRenderNoteContent($content, $content_type, &$rendered): EventResult
public function onRenderNoteContent(string $content, string $content_type, string &$rendered): EventResult
{
if ($content_type !== 'application/x-latex') {
return Event::next;

View File

@ -43,7 +43,8 @@ class MarkdownNotes extends Plugin
$types['Markdown'] = 'text/markdown';
return Event::next;
}
public function onRenderNoteContent($content, $content_type, &$rendered): EventResult
public function onRenderNoteContent(string $content, string $content_type, string &$rendered): EventResult
{
if ($content_type !== 'text/markdown') {
return Event::next;

View File

@ -140,7 +140,7 @@ class NoteTypeFeedFilter extends Plugin
/**
* Draw the media feed navigation.
*/
public function onAddFeedActions(Request $request, bool $is_not_empty, &$res): EventResult
public function onAddFeedActions(Request $request, bool $is_not_empty, array &$res): EventResult
{
$qs = [];
$query_string = $request->getQueryString();

View File

@ -36,6 +36,9 @@ use League\OAuth2\Server\CryptKey;
use League\OAuth2\Server\Entities\AccessTokenEntityInterface;
use Plugin\OAuth2\Util\Token;
/**
* @extends Token<AccessToken>
*/
class AccessToken extends Token implements AccessTokenEntityInterface
{
// {{{ Autocode

View File

@ -36,6 +36,9 @@ use League\OAuth2\Server\Entities\AuthCodeEntityInterface;
use Plugin\OAuth2\Repository;
use Plugin\OAuth2\Util\Token;
/**
* @extends Token<AuthCode>
*/
class AuthCode extends Token implements AuthCodeEntityInterface
{
// {{{ Autocode

View File

@ -45,11 +45,17 @@ class RefreshToken implements RefreshTokenRepositoryInterface
DB::persist($refreshTokenEntity);
}
/**
* @param string $tokenId
*/
public function revokeRefreshToken($tokenId)
{
// Some logic to revoke the auth token in a database
}
/**
* @param string $tokenId
*/
public function isRefreshtokenRevoked($tokenId): bool
{
return false; // The auth token has not been revoked

View File

@ -29,6 +29,7 @@ use function App\Core\I18n\_m;
use App\Core\Router;
use App\Entity\Actor;
use App\Entity\LocalUser;
use App\Entity\Note;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\NoSuchNoteException;
@ -39,6 +40,9 @@ use Plugin\PinnedNotes\Entity as E;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request;
/**
* @extends FeedController<Note>
*/
class PinnedNotes extends FeedController
{
public function listPinsByNickname(Request $request, string $nickname)

View File

@ -16,7 +16,8 @@ class PinnedNotes extends Entity
{
return $this->id;
}
public function setId($id)
public function setId(int $id): self
{
$this->id = $id;
return $this;
@ -26,7 +27,8 @@ class PinnedNotes extends Entity
{
return $this->actor_id;
}
public function setActorId($actor_id)
public function setActorId(int $actor_id): self
{
$this->actor_id = $actor_id;
return $this;
@ -36,7 +38,8 @@ class PinnedNotes extends Entity
{
return $this->note_id;
}
public function setNoteId($note_id)
public function setNoteId(int $note_id): self
{
$this->note_id = $note_id;
return $this;

View File

@ -74,7 +74,7 @@ class PinnedNotes extends Plugin
return Event::next;
}
public function onBeforeFeed(Request $request, &$res): EventResult
public function onBeforeFeed(Request $request, array &$res): EventResult
{
$path = $request->attributes->get('_route');
if ($path === 'actor_view_nickname') {
@ -160,7 +160,7 @@ class PinnedNotes extends Plugin
return Event::next;
}
public function onActivityPubAddActivityStreamsTwoData(string $type_name, &$type): EventResult
public function onActivityPubAddActivityStreamsTwoData(string $type_name, object &$type): EventResult
{
if ($type_name === 'Person') {
$actor = \Plugin\ActivityPub\Util\Explorer::getOneFromUri($type->get('id'));

View File

@ -83,7 +83,7 @@ class UnboundGroup extends Plugin
return Event::next;
}
public function onActivityPubAddActivityStreamsTwoData(string $type_name, &$type): EventResult
public function onActivityPubAddActivityStreamsTwoData(string $type_name, object &$type): EventResult
{
if ($type_name === 'Group' || $type_name === 'Organization') {
$actor = \Plugin\ActivityPub\Util\Explorer::getOneFromUri($type->getId());

View File

@ -55,7 +55,7 @@ class WebMonetization extends Plugin
{
/**
* @param array<string, mixed> $vars
* @param string[] $res
* @param string[] $res
*/
public function onAppendRightPanelBlock(Request $request, array $vars, array &$res): EventResult
{
@ -63,9 +63,6 @@ class WebMonetization extends Plugin
if (\is_null($user)) {
return Event::next;
}
if (\is_null($vars)) {
return Event::next;
}
$is_self = null;
$receiver_id = null;
@ -214,7 +211,7 @@ class WebMonetization extends Plugin
];
}
public function onAppendToHead(Request $request, &$res): EventResult
public function onAppendToHead(Request $request, array &$res): EventResult
{
$user = Common::user();
if (\is_null($user)) {
@ -257,7 +254,7 @@ class WebMonetization extends Plugin
return Event::next;
}
public function onActivityPubAddActivityStreamsTwoData(string $type_name, &$type): EventResult
public function onActivityPubAddActivityStreamsTwoData(string $type_name, object &$type): EventResult
{
if ($type_name === 'Person') {
$actor = \Plugin\ActivityPub\Util\Explorer::getOneFromUri($type->getId());

View File

@ -40,7 +40,7 @@ use EventResult;
class XMPPNotifications extends Plugin
{
public function onAddNotificationTransport(&$form_defs): EventResult
public function onAddNotificationTransport(array &$form_defs): EventResult
{
$form_defs['XMPP'] = $form_defs['placeholder'];
$form_defs['XMPP'][] = $form_defs['placeholder']['save']('XMPP', 'save_xmpp');

View File

@ -40,8 +40,12 @@ use Symfony\Component\Cache\CacheItem;
abstract class Cache
{
protected static $pools;
protected static $redis;
protected static array $pools;
/**
* @property ?Redis $redis
*/
protected static mixed $redis;
/**
* Configure a cache pool, with adapters taken from `ENV_VAR`.
@ -77,8 +81,10 @@ abstract class Cache
// @codeCoverageIgnoreStart
// This requires extra server configuration, but the code was tested
// manually and works, so it'll be excluded from automatic tests, for now, at least
if (F\Every($dsns, function ($str) { [$scheme, $rest] = explode('://', $str);
return str_contains($rest, ':'); }) == false) {
if (F\Every($dsns, function ($str) {
[$scheme, $rest] = explode('://', $str);
return str_contains($rest, ':');
}) == false) {
throw new ConfigurationException('The configuration of a redis cluster requires specifying the ports to use');
}
$class = RedisCluster::class; // true for persistent connection

View File

@ -153,7 +153,8 @@ abstract class Controller extends AbstractController implements EventSubscriberI
$controller = $controller[0];
}
if (is_subclass_of($controller, FeedController::class)) {
$this->vars = $controller->postProcess($this->vars);
// XXX
$this->vars = $controller->postProcess($this->vars); // @phpstan-ignore-line
}
// Respond in the most preferred acceptable content type
@ -162,10 +163,10 @@ abstract class Controller extends AbstractController implements EventSubscriberI
$format = $request->getFormat($accept[0]);
$potential_response = null;
if (Event::handle('ControllerResponseInFormat', [
'route' => $route,
'route' => $route,
'accept_header' => $accept,
'vars' => $this->vars,
'response' => &$potential_response,
'vars' => $this->vars,
'response' => &$potential_response,
]) !== Event::stop) {
if ($redirect !== false) {
$event->setResponse(new RedirectResponse($redirect));
@ -274,7 +275,7 @@ abstract class Controller extends AbstractController implements EventSubscriberI
// @codeCoverageIgnoreStart
Log::critical($m = "Method '{$method}' on class App\\Core\\Controller not found (__call)");
throw new RuntimeException($m);
// @codeCoverageIgnoreEnd
// @codeCoverageIgnoreEnd
}
}
}

View File

@ -45,6 +45,7 @@ use Doctrine\ORM\EntityManager;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Query;
use Doctrine\ORM\Query\ResultSetMappingBuilder;
use Doctrine\ORM\QueryBuilder;
@ -89,8 +90,8 @@ use Functional as F;
*/
class DB
{
private static ?EntityManagerInterface $em;
public static function setManager($m): void
private static null|\Doctrine\ORM\EntityManagerInterface|\Doctrine\Persistence\ObjectManager $em;
public static function setManager(EntityManagerInterface|\Doctrine\Persistence\ObjectManager $m): void
{
self::$em = $m;
}
@ -104,6 +105,7 @@ class DB
private static ?array $dql_table_name_patterns = null;
public static function initTableMap()
{
/** @var ClassMetadataInfo[] $all */ // @phpstan-ignore-next-line
$all = self::$em->getMetadataFactory()->getAllMetadata();
foreach ($all as $meta) {
self::$table_map[$meta->getTableName()] = $meta->getMetadataValue('name');
@ -130,7 +132,7 @@ class DB
public static function dql(string $query, array $params = [], array $options = [])
{
$query = preg_replace(self::$dql_table_name_patterns, self::$table_map, $query);
$q = new Query(self::$em);
$q = new Query(self::$em); // @phpstan-ignore-line
$q->setDQL($query);
if (isset($options['limit'])) {
@ -192,6 +194,7 @@ class DB
if ($_ENV['APP_ENV'] === 'dev' && str_starts_with($query, 'select *')) {
throw new Exception('Cannot use `select *`, use `select {select}` (see ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT)');
}
// @phpstan-ignore-next-line
$rsmb = new ResultSetMappingBuilder(self::$em, \is_null($entities) ? ResultSetMappingBuilder::COLUMN_RENAMING_INCREMENT : ResultSetMappingBuilder::COLUMN_RENAMING_NONE);
if (\is_null($entities)) {
$matches = [];
@ -205,7 +208,8 @@ class DB
$rsmb->addRootEntityFromClassMetadata($entity, $alias);
}
$query = preg_replace('/{select}/', $rsmb->generateSelectClause(), $query);
$q = self::$em->createNativeQuery($query, $rsmb);
// @phpstan-ignore-next-line
$q = self::$em->createNativeQuery($query, $rsmb);
foreach ($params as $k => $v) {
$q->setParameter($k, $v);
}
@ -263,7 +267,7 @@ class DB
{
$criteria = array_change_key_case($criteria, \CASE_LOWER);
$ops = array_intersect(array_keys($criteria), self::$find_by_ops);
/** @var EntityRepository */
/** @var EntityRepository */ // @phpstan-ignore-next-line
$repo = self::getRepository($table);
if (empty($ops)) {
return $repo->findBy($criteria, $order_by, $limit, $offset);
@ -307,7 +311,7 @@ class DB
public static function count(string $table, array $criteria)
{
/** @var EntityRepository */
/** @var EntityRepository */ // @phpstan-ignore-next-line
$repo = self::getRepository($table);
return $repo->count($criteria);
}

View File

@ -32,9 +32,7 @@ declare(strict_types = 1);
namespace App\Core;
use App\Core\DB;
use function App\Core\I18n\_m;
use App\Core\Router;
use App\Util\Common;
use App\Util\Exception\ClientException;
use App\Util\Exception\ServerException;
@ -82,7 +80,7 @@ use Symfony\Component\HttpFoundation\Request;
abstract class Form
{
private static ?FormFactoryInterface $form_factory;
public static function setFactory($ff): void
public static function setFactory(FormFactoryInterface $ff): void
{
self::$form_factory = $ff;
}

View File

@ -204,7 +204,7 @@ class GSFile
* Throw a client exception if the cache key $id doesn't contain
* exactly one entry
*/
public static function error($exception, $id, array $res)
public static function error(string $exception, int $id, array $res): mixed
{
switch (\count($res)) {
case 0:

View File

@ -66,7 +66,7 @@ abstract class I18n
{
public static ?TranslatorInterface $translator = null;
public static function setTranslator($trans): void
public static function setTranslator(TranslatorInterface $trans): void
{
self::$translator = $trans;
}
@ -306,9 +306,11 @@ abstract class I18n
* _m(string|string[] $msg, array $params) -- parameterized message
* _m(string $ctx, string|string[] $msg, array $params) -- combination of the previous two
*
* @throws ServerException
*
* @todo add parameters
*
* @param array|int|string ...$args
*
* @throws ServerException
*/
function _m(...$args): string
{
@ -317,18 +319,18 @@ function _m(...$args): string
// and only 2 frames (this and previous)
$domain = I18n::_mdomain(debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 2)[0]['file']);
switch (\count($args)) {
case 1:
// Empty parameters, simple message
return I18n::$translator->trans($args[0], [], $domain, Common::currentLanguage()->getLocale());
case 3:
// @codeCoverageIgnoreStart
if (\is_int($args[2])) {
throw new InvalidArgumentException('Calling `_m()` with a number for pluralization is deprecated, '
. 'use an explicit parameter', );
}
// @codeCoverageIgnoreEnd
// Falthrough
// no break
case 1:
// Empty parameters, simple message
return I18n::$translator->trans($args[0], [], $domain, Common::currentLanguage()->getLocale());
case 3:
// @codeCoverageIgnoreStart
if (\is_int($args[2])) {
throw new InvalidArgumentException('Calling `_m()` with a number for pluralization is deprecated, '
. 'use an explicit parameter', );
}
// @codeCoverageIgnoreEnd
// Falthrough
// no break
case 2:
if (\is_array($args[0])) {
$args[0] = I18n::formatICU($args[0], $args[1]);
@ -339,12 +341,12 @@ function _m(...$args): string
$params = $args[1] ?? [];
return I18n::$translator->trans($msg, $params, $domain, Common::currentLanguage()->getLocale());
}
// Fallthrough
// no break
default:
// @codeCoverageIgnoreStart
throw new InvalidArgumentException("Bad parameters to `_m()` for domain {$domain}");
// @codeCoverageIgnoreEnd
// Fallthrough
// no break
default:
// @codeCoverageIgnoreStart
throw new InvalidArgumentException("Bad parameters to `_m()` for domain {$domain}");
// @codeCoverageIgnoreEnd
}
}

View File

@ -127,6 +127,8 @@ class TransExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* Normalizes a token.
*
* @param string $token
*/
protected function normalizeToken($token): ?string
{
@ -186,6 +188,8 @@ class TransExtractor extends AbstractFileExtractor implements ExtractorInterface
/**
* {@inheritDoc}
*
* @param string $directory
*/
protected function extractFromDirectory($directory): iterable
{

View File

@ -53,7 +53,7 @@ abstract class Log
{
private static ?LoggerInterface $logger;
public static function setLogger($l): void
public static function setLogger(LoggerInterface $l): void
{
self::$logger = $l;
}
@ -62,6 +62,7 @@ abstract class Log
* Log a critical error when a really unexpected exception occured. This indicates a bug in the software
*
* @throws ServerException
*
* @codeCoverageIgnore
*/
public static function unexpected_exception(Exception $e)

View File

@ -52,11 +52,11 @@ use Symfony\Component\DependencyInjection\Reference;
class ModuleManager
{
protected static $loader;
protected static \Composer\Autoload\ClassLoader $loader;
/**
* @codeCoverageIgnore
*/
public static function setLoader($l)
public static function setLoader(\Composer\Autoload\ClassLoader $l)
{
self::$loader = $l;
}
@ -161,7 +161,7 @@ class ModuleManager
/**
* Serialize this class, for dumping into the cache
*/
public static function __set_state($state)
public static function __set_state(array $state): self
{
$obj = new self();
$obj->modules = $state['modules'];

View File

@ -54,7 +54,7 @@ abstract class Module
/**
* Serialize the class to store in the cache
*/
public static function __set_state($state)
public static function __set_state(array $state)
{
$obj = new (static::class);
foreach ($state as $k => $v) {

View File

@ -40,7 +40,7 @@ abstract class Queue
{
private static ?MessageBusInterface $message_bus;
public static function setMessageBus($mb): void
public static function setMessageBus(MessageBusInterface $mb): void
{
self::$message_bus = $mb;
}
@ -50,7 +50,7 @@ abstract class Queue
*
* @codeCoverageIgnore
*/
public static function enqueue($payload, string $queue, bool $priority = false, array $stamps = [])
public static function enqueue(mixed $payload, string $queue, bool $priority = false, array $stamps = []): void
{
if ($priority) {
self::$message_bus->dispatch(new MessageHigh($payload, $queue), $stamps);

View File

@ -32,8 +32,6 @@ declare(strict_types = 1);
namespace App\Core;
use App\Core\Event;
use App\Core\Log;
use App\Util\Common;
use Symfony\Component\Config\Loader\Loader;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;
@ -41,6 +39,7 @@ use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Router as SymfonyRouter;
use Symfony\Component\Routing\RouterInterface;
/**
* @mixin SymfonyRouter
@ -70,9 +69,9 @@ class Router extends Loader
*/
public const NETWORK_PATH = UrlGeneratorInterface::NETWORK_PATH;
public static ?SymfonyRouter $router = null;
public static ?RouterInterface $router = null;
public static function setRouter($rtr): void
public static function setRouter(RouterInterface $rtr): void
{
self::$router = $rtr;
}
@ -122,6 +121,7 @@ class Router extends Loader
*
* Must conform to symfony's interface, but the $resource is unused
* and $type must not be null
* @param string $resource
*/
public function load($resource, ?string $type = null): RouteCollection
{
@ -193,11 +193,11 @@ class Router extends Loader
defaults: array_merge(
[
'_controller' => \is_array($target) ? $target : [$target, '__invoke'],
'_format' => $options['format'] ?? 'html',
'_fragment' => $options['fragment'] ?? '',
'_locale' => $options['locale'] ?? 'en',
'template' => $options['template'] ?? '',
'accept' => $options['accept'] ?? [],
'_format' => $options['format'] ?? 'html',
'_fragment' => $options['fragment'] ?? '',
'_locale' => $options['locale'] ?? 'en',
'template' => $options['template'] ?? '',
'accept' => $options['accept'] ?? [],
'is_system_path' => $options['is_system_path'] ?? true,
],
$options['defaults'] ?? [],

View File

@ -50,6 +50,8 @@ class RouteLoader extends Loader
*
* Must conform to symfony's interface, but the $resource is unused
* and $type must not be null
*
* @param resource $resource
*/
public function load($resource, ?string $type = null): RouteCollection
{
@ -121,11 +123,11 @@ class RouteLoader extends Loader
defaults: array_merge(
[
'_controller' => \is_array($target) ? $target : [$target, '__invoke'],
'_format' => $options['format'] ?? 'html',
'_fragment' => $options['fragment'] ?? '',
'_locale' => $options['locale'] ?? 'en',
'template' => $options['template'] ?? '',
'accept' => $options['accept'] ?? [],
'_format' => $options['format'] ?? 'html',
'_fragment' => $options['fragment'] ?? '',
'_locale' => $options['locale'] ?? 'en',
'template' => $options['template'] ?? '',
'accept' => $options['accept'] ?? [],
'is_system_path' => $options['is_system_path'] ?? true,
],
$options['defaults'] ?? [],

View File

@ -46,6 +46,7 @@ use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
* HtmlSanitizer\SanitizerInterface, calling the first existing method, in that order
*
* @codeCoverageIgnore
*
* @mixin SymfonySecurity
*
* @method static LocalUser getUser()
@ -53,7 +54,7 @@ use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
class Security implements EventSubscriberInterface //implements AuthenticatorInterface
{
private static ?SymfonySecurity $security;
public static function setHelper($sec): void
public static function setHelper(SymfonySecurity $sec): void
{
self::$security = $sec;
}

View File

@ -93,8 +93,11 @@ class SchemaDefDriver extends StaticPHPDriver implements CompilerPassInterface
/**
* Fill in the database $metadata for $class_name
*
* @param class-string $class_name
* @param ClassMetadataInfo $metadata ClassMetadataInfo is the real type, but we need to override the method
* @param class-string $class_name
*
* @phpstan-ignore-next-line
*
* @param ClassMetadataInfo $metadata ClassMetadataInfo is the real type, but we need to override the method
*/
public function loadMetadataForClass($class_name, ClassMetadata $metadata): void
{
@ -143,23 +146,23 @@ class SchemaDefDriver extends StaticPHPDriver implements CompilerPassInterface
];
switch ($opts['multiplicity']) {
case 'one to one':
$metadata->mapOneToOne($map);
break;
case 'many to one':
$metadata->mapManyToOne($map);
break;
case 'one to many':
$map['mappedBy'] = $target_field;
$metadata->mapOneToMany($map);
break;
case 'many to many':
$metadata->mapManyToMany($map);
break;
default:
throw new Exception("Invalid multiplicity specified: '${opts['multiplicity']}' in class: {$class_name}");
case 'one to one':
$metadata->mapOneToOne($map);
break;
case 'many to one':
$metadata->mapManyToOne($map);
break;
case 'one to many':
$map['mappedBy'] = $target_field;
$metadata->mapOneToMany($map);
break;
case 'many to many':
$metadata->mapManyToMany($map);
break;
default:
throw new Exception("Invalid multiplicity specified: '${opts['multiplicity']}' in class: {$class_name}");
}
// @codeCoverageIgnoreEnd
// @codeCoverageIgnoreEnd
} else {
// Convert old to new types
// For ints, prepend the size (smallint)
@ -213,6 +216,8 @@ class SchemaDefDriver extends StaticPHPDriver implements CompilerPassInterface
* Override StaticPHPDriver's method,
* we care about classes that have the method `schemaDef`,
* instead of `loadMetadata`.
*
* @param mixed $class_name
*/
public function isTransient($class_name): bool
{

View File

@ -550,6 +550,10 @@ class Actor extends Entity
}
}
public function hasBlocked(self $other): bool {
return false;
}
public function __call(string $name, array $arguments): mixed
{
if (Formatting::startsWith($name, 'is')) {

View File

@ -17,6 +17,8 @@ use SymfonyCasts\Bundle\ResetPassword\Persistence\ResetPasswordRequestRepository
* @method null|ResetPasswordRequest findOneBy(array $criteria, array $orderBy = null)
* @method ResetPasswordRequest[] findAll()
* @method ResetPasswordRequest[] findBy(array $criteria, array $orderBy = null, $limit = null, $offset = null)
*
* @extends ServiceEntityRepository<ResetPasswordRequest>
*/
class ResetPasswordRequestRepository extends ServiceEntityRepository implements ResetPasswordRequestRepositoryInterface
{

View File

@ -116,6 +116,8 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface
/**
* @codeCoverageIgnore
*
* @param null|string ...$args
*/
public function getConfig(...$args)
{
@ -125,10 +127,11 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface
/**
* get stylesheets
*
* @return array|mixed
* @codeCoverageIgnore
*
* @return array<string>
*/
public function getShowStylesheets($route)
public function getShowStylesheets(string $route): array
{
$styles = [];
Event::handle('EndShowStyles', [&$styles, $route]);
@ -137,8 +140,11 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface
/**
* @codeCoverageIgnore
*
* @param mixed ...$args
* @return mixed[]
*/
public function handleEvent(string $event, ...$args)
public function handleEvent(string $event, ...$args): array
{
$res = [];
$args[] = &$res;
@ -164,7 +170,7 @@ class Runtime implements RuntimeExtensionInterface, EventSubscriberInterface
&& (preg_match(pattern: $re_has_gecko, subject: $this->request->headers->get('User-Agent')) === 1);
}
public function isInstanceOf($value, string $type): bool
public function isInstanceOf(mixed $value, string $type): bool
{
return (\function_exists($func = 'is_' . $type) && $func($value)) || $value instanceof $type;
}

View File

@ -108,7 +108,7 @@ abstract class Common
*
* @param bool $transient keep this setting in memory only
*/
public static function setConfig(string $section, string $setting, $value, bool $transient = false): void
public static function setConfig(string $section, string $setting, mixed $value, bool $transient = false): void
{
self::$config[$section][$setting] = $value;
if (!$transient) {
@ -184,8 +184,13 @@ abstract class Common
/**
* A recursive `array_diff`, while PHP itself doesn't provide one
*
* @param mixed[] $array1
* @param mixed[] $array2
*
* @return mixed[]
*/
public static function arrayDiffRecursive($array1, $array2): array
public static function arrayDiffRecursive(array $array1, array $array2): array
{
$diff = [];
foreach ($array1 as $key => $value) {

View File

@ -191,7 +191,7 @@ abstract class Formatting
/**
* Convert scalars, objects implementing __toString or arrays to strings
*/
public static function toString($value, string $join_type = self::JOIN_BY_COMMA): string
public static function toString(mixed $value, string $join_type = self::JOIN_BY_COMMA): string
{
if (!\in_array($join_type, [static::JOIN_BY_SPACE, static::JOIN_BY_COMMA])) {
throw new Exception('Formatting::toString received invalid join option');
@ -209,7 +209,7 @@ abstract class Formatting
*
* @param static::SPLIT_BY_BOTH|static::SPLIT_BY_COMMA|static::SPLIT_BY_SPACE $split_type
*/
public static function toArray(string $input, &$output, string $split_type = self::SPLIT_BY_COMMA): bool
public static function toArray(string $input, array &$output, string $split_type = self::SPLIT_BY_COMMA): bool
{
if ($input == '') {
$output = [];

View File

@ -57,7 +57,7 @@ abstract class HTML
public const SELF_CLOSING_TAG = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
private static ?SanitizerInterface $sanitizer;
public static function setSanitizer($sanitizer): void
public static function setSanitizer(SanitizerInterface $sanitizer): void
{
self::$sanitizer = $sanitizer;
}

View File

@ -42,7 +42,9 @@ use Symfony\Component\Mime\MimeTypes;
*/
class TemporaryFile extends SplFileInfo
{
// Cannot type annotate currently. `resource` is the expected type, but it's not a builtin type
/**
* @var null|false|resource $resource
*/
protected $resource;
/**