diff --git a/extensions/flags/src/Api/Resource/FlagResource.php b/extensions/flags/src/Api/Resource/FlagResource.php index 2d2a58352..a124abd52 100644 --- a/extensions/flags/src/Api/Resource/FlagResource.php +++ b/extensions/flags/src/Api/Resource/FlagResource.php @@ -28,6 +28,9 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Arr; use Tobyz\JsonApiServer\Context; +/** + * @extends AbstractDatabaseResource + */ class FlagResource extends AbstractDatabaseResource { public function __construct( diff --git a/extensions/sticky/src/Api/DiscussionResourceFields.php b/extensions/sticky/src/Api/DiscussionResourceFields.php index b23d5a0ca..ca4987d90 100644 --- a/extensions/sticky/src/Api/DiscussionResourceFields.php +++ b/extensions/sticky/src/Api/DiscussionResourceFields.php @@ -18,7 +18,7 @@ use Flarum\Sticky\Event\DiscussionWasUnstickied; class DiscussionResourceFields { - public function __invoke() + public function __invoke(): array { return [ Schema\Boolean::make('isSticky') diff --git a/extensions/sticky/src/PinStickiedDiscussionsToTop.php b/extensions/sticky/src/PinStickiedDiscussionsToTop.php index e5028ab18..fd245b3f8 100755 --- a/extensions/sticky/src/PinStickiedDiscussionsToTop.php +++ b/extensions/sticky/src/PinStickiedDiscussionsToTop.php @@ -17,7 +17,6 @@ class PinStickiedDiscussionsToTop { public function __invoke(DatabaseSearchState $state, SearchCriteria $criteria): void { - return; if ($criteria->sortIsDefault && ! $state->isFulltextSearch()) { $query = $state->getQuery(); diff --git a/extensions/tags/extend.php b/extensions/tags/extend.php index 35345ee33..0bf0d0c17 100644 --- a/extensions/tags/extend.php +++ b/extensions/tags/extend.php @@ -31,6 +31,7 @@ use Flarum\Tags\Search\HideHiddenTagsFromAllDiscussionsPage; use Flarum\Tags\Search\TagSearcher; use Flarum\Tags\Tag; use Flarum\Tags\Utf8SlugDriver; +use Illuminate\Database\Eloquent\Builder; return [ (new Extend\Frontend('forum')) @@ -107,7 +108,8 @@ return [ function (Endpoint\Index|Endpoint\Show|Endpoint\Create $endpoint) { return $endpoint ->addDefaultInclude(['tags', 'tags.parent']) - ->eagerLoadWhere('tags', function ($query, Context $context) { + ->eagerLoadWhere('tags', function (Builder $query, Context $context, array $relations) { + /** @var Builder $query */ $query->withStateFor($context->getActor()); }); } diff --git a/extensions/tags/src/Api/Resource/TagResource.php b/extensions/tags/src/Api/Resource/TagResource.php index b625c42cd..c5616cc24 100644 --- a/extensions/tags/src/Api/Resource/TagResource.php +++ b/extensions/tags/src/Api/Resource/TagResource.php @@ -9,6 +9,7 @@ namespace Flarum\Tags\Api\Resource; +use Flarum\Api\Context as FlarumContext; use Flarum\Api\Endpoint; use Flarum\Api\Resource\AbstractDatabaseResource; use Flarum\Api\Schema; @@ -21,6 +22,9 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Arr; use Tobyz\JsonApiServer\Context; +/** + * @extends AbstractDatabaseResource + */ class TagResource extends AbstractDatabaseResource { public function __construct( @@ -107,7 +111,7 @@ class TagResource extends AbstractDatabaseResource ->writable(), Schema\Boolean::make('isRestricted') ->writableOnUpdate() - ->visible(fn (Tag $tag, Context $context) => $context->getActor()->isAdmin()), + ->visible(fn (Tag $tag, FlarumContext $context) => $context->getActor()->isAdmin()), Schema\Str::make('backgroundUrl') ->get(fn (Tag $tag) => $tag->background_path), Schema\Str::make('backgroundMode'), @@ -119,14 +123,14 @@ class TagResource extends AbstractDatabaseResource ->get(fn (Tag $tag) => (bool) $tag->parent_id), Schema\DateTime::make('lastPostedAt'), Schema\Boolean::make('canStartDiscussion') - ->get(fn (Tag $tag, Context $context) => $context->getActor()->can('startDiscussion', $tag)), + ->get(fn (Tag $tag, FlarumContext $context) => $context->getActor()->can('startDiscussion', $tag)), Schema\Boolean::make('canAddToDiscussion') - ->get(fn (Tag $tag, Context $context) => $context->getActor()->can('addToDiscussion', $tag)), + ->get(fn (Tag $tag, FlarumContext $context) => $context->getActor()->can('addToDiscussion', $tag)), Schema\Relationship\ToOne::make('parent') ->type('tags') ->includable() - ->writable(fn (Tag $tag, Context $context) => (bool) Arr::get($context->body(), 'attributes.isPrimary')), + ->writable(fn (Tag $tag, FlarumContext $context) => (bool) Arr::get($context->body(), 'attributes.isPrimary')), Schema\Relationship\ToMany::make('children') ->type('tags') ->includable(), diff --git a/framework/core/src/Api/Controller/UploadFaviconController.php b/framework/core/src/Api/Controller/UploadFaviconController.php index c251a64c0..35c3d1ad1 100644 --- a/framework/core/src/Api/Controller/UploadFaviconController.php +++ b/framework/core/src/Api/Controller/UploadFaviconController.php @@ -9,6 +9,7 @@ namespace Flarum\Api\Controller; +use Flarum\Api\JsonApi; use Flarum\Foundation\ValidationException; use Flarum\Locale\TranslatorInterface; use Flarum\Settings\SettingsRepositoryInterface; @@ -23,12 +24,13 @@ class UploadFaviconController extends UploadImageController protected string $filenamePrefix = 'favicon'; public function __construct( + JsonApi $api, SettingsRepositoryInterface $settings, Factory $filesystemFactory, protected TranslatorInterface $translator, protected ImageManager $imageManager ) { - parent::__construct($settings, $filesystemFactory); + parent::__construct($api, $settings, $filesystemFactory); } protected function makeImage(UploadedFileInterface $file): EncodedImageInterface diff --git a/framework/core/src/Api/Controller/UploadLogoController.php b/framework/core/src/Api/Controller/UploadLogoController.php index 1ca056bc6..396e14acb 100644 --- a/framework/core/src/Api/Controller/UploadLogoController.php +++ b/framework/core/src/Api/Controller/UploadLogoController.php @@ -9,6 +9,7 @@ namespace Flarum\Api\Controller; +use Flarum\Api\JsonApi; use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Filesystem\Factory; use Intervention\Image\ImageManager; @@ -21,11 +22,12 @@ class UploadLogoController extends UploadImageController protected string $filenamePrefix = 'logo'; public function __construct( + JsonApi $api, SettingsRepositoryInterface $settings, Factory $filesystemFactory, protected ImageManager $imageManager ) { - parent::__construct($settings, $filesystemFactory); + parent::__construct($api, $settings, $filesystemFactory); } protected function makeImage(UploadedFileInterface $file): EncodedImageInterface diff --git a/framework/core/src/Api/Endpoint/Concerns/ExtractsListingParams.php b/framework/core/src/Api/Endpoint/Concerns/ExtractsListingParams.php index 56644b118..1b7a44b83 100644 --- a/framework/core/src/Api/Endpoint/Concerns/ExtractsListingParams.php +++ b/framework/core/src/Api/Endpoint/Concerns/ExtractsListingParams.php @@ -12,6 +12,7 @@ namespace Flarum\Api\Endpoint\Concerns; use Closure; use Flarum\Http\RequestUtil; use Tobyz\JsonApiServer\Context; +use Tobyz\JsonApiServer\Resource\AbstractResource; use Tobyz\JsonApiServer\Schema\Sort; trait ExtractsListingParams @@ -109,6 +110,10 @@ trait ExtractsListingParams public function getAvailableSorts(Context $context): array { + if (! $context->collection instanceof AbstractResource) { + return []; + } + $asc = collect($context->collection->resolveSorts()) ->filter(fn (Sort $field) => $field->isVisible($context)) ->pluck('name') diff --git a/framework/core/src/Api/Endpoint/Concerns/HasAuthorization.php b/framework/core/src/Api/Endpoint/Concerns/HasAuthorization.php index d645700b6..aa4abfa0b 100644 --- a/framework/core/src/Api/Endpoint/Concerns/HasAuthorization.php +++ b/framework/core/src/Api/Endpoint/Concerns/HasAuthorization.php @@ -17,14 +17,8 @@ use Tobyz\JsonApiServer\Context; trait HasAuthorization { - /** - * @var bool|(Closure(mixed, Context): bool) - */ protected bool|Closure $authenticated = false; - /** - * @var null|string|Closure(mixed, Context): string - */ protected null|string|Closure $ability = null; protected bool $admin = false; @@ -67,9 +61,9 @@ trait HasAuthorization return $this->ability; } - return (bool) (isset($context->model) + return isset($context->model) ? ($this->ability)($context->model, $context) - : ($this->ability)($context)); + : ($this->ability)($context); } /** diff --git a/framework/core/src/Api/Endpoint/EndpointInterface.php b/framework/core/src/Api/Endpoint/EndpointInterface.php index 2bcd1aa09..56ea81c24 100644 --- a/framework/core/src/Api/Endpoint/EndpointInterface.php +++ b/framework/core/src/Api/Endpoint/EndpointInterface.php @@ -9,6 +9,9 @@ namespace Flarum\Api\Endpoint; +/** + * @mixin \Tobyz\JsonApiServer\Endpoint\Endpoint + */ interface EndpointInterface { // diff --git a/framework/core/src/Api/Resource/AbstractDatabaseResource.php b/framework/core/src/Api/Resource/AbstractDatabaseResource.php index 488ef3dcb..48ce79a25 100644 --- a/framework/core/src/Api/Resource/AbstractDatabaseResource.php +++ b/framework/core/src/Api/Resource/AbstractDatabaseResource.php @@ -9,35 +9,22 @@ namespace Flarum\Api\Resource; +use Flarum\Api\Context as FlarumContext; use Flarum\Api\Resource\Concerns\Bootable; use Flarum\Api\Resource\Concerns\Extendable; use Flarum\Api\Resource\Concerns\HasSortMap; -use Flarum\Api\Resource\Contracts\{ - Countable, - Creatable, - Deletable, - Findable, - Listable, - Paginatable, - Resource, - Updatable -}; use Flarum\Foundation\DispatchEventsTrait; use Flarum\User\User; +use Illuminate\Database\Eloquent\Model; use RuntimeException; use Tobyz\JsonApiServer\Context; use Tobyz\JsonApiServer\Laravel\EloquentResource as BaseResource; -abstract class AbstractDatabaseResource extends BaseResource implements - Resource, - Findable, - Listable, - Countable, - Paginatable, - Creatable, - Updatable, - Deletable -{ +/** + * @template M of Model + * @extends BaseResource + */ +abstract class AbstractDatabaseResource extends BaseResource { use Bootable; use Extendable; use HasSortMap; @@ -47,6 +34,7 @@ abstract class AbstractDatabaseResource extends BaseResource implements abstract public function model(): string; + /** @inheritDoc */ public function newModel(Context $context): object { return new ($this->model()); @@ -97,41 +85,79 @@ abstract class AbstractDatabaseResource extends BaseResource implements $this->dispatchEventsFor($model, $context->getActor()); } + /** + * @param M $model + * @param FlarumContext $context + * @return M|null + */ public function creating(object $model, Context $context): ?object { return $model; } + /** + * @param M $model + * @param FlarumContext $context + * @return M|null + */ public function updating(object $model, Context $context): ?object { return $model; } + /** + * @param M $model + * @param FlarumContext $context + * @return M|null + */ public function saving(object $model, Context $context): ?object { return $model; } + /** + * @param M $model + * @param FlarumContext $context + * @return M|null + */ public function saved(object $model, Context $context): ?object { return $model; } + /** + * @param M $model + * @param FlarumContext $context + * @return M|null + */ public function created(object $model, Context $context): ?object { return $model; } + /** + * @param M $model + * @param FlarumContext $context + * @return M|null + */ public function updated(object $model, Context $context): ?object { return $model; } + /** + * @param M $model + * @param FlarumContext $context + */ public function deleting(object $model, Context $context): void { // } + /** + * @param M $model + * @param FlarumContext $context + */ public function deleted(object $model, Context $context): void { // @@ -144,11 +170,17 @@ abstract class AbstractDatabaseResource extends BaseResource implements } } + /** + * @param FlarumContext $context + */ public function mutateDataBeforeValidation(Context $context, array $data): array { return $data; } + /** + * @param FlarumContext $context + */ public function results(object $query, Context $context): iterable { if ($results = $context->getSearchResults()) { @@ -158,6 +190,9 @@ abstract class AbstractDatabaseResource extends BaseResource implements return $query->get(); } + /** + * @param FlarumContext $context + */ public function count(object $query, Context $context): ?int { if ($results = $context->getSearchResults()) { diff --git a/framework/core/src/Api/Resource/AbstractResource.php b/framework/core/src/Api/Resource/AbstractResource.php index 6754a9308..d203c4865 100644 --- a/framework/core/src/Api/Resource/AbstractResource.php +++ b/framework/core/src/Api/Resource/AbstractResource.php @@ -9,14 +9,17 @@ namespace Flarum\Api\Resource; +use Flarum\Api\Context; use Flarum\Api\Resource\Concerns\Bootable; use Flarum\Api\Resource\Concerns\Extendable; use Flarum\Api\Resource\Concerns\HasSortMap; -use Flarum\Api\Resource\Contracts\Collection; -use Flarum\Api\Resource\Contracts\Resource; use Tobyz\JsonApiServer\Resource\AbstractResource as BaseResource; -abstract class AbstractResource extends BaseResource implements Resource, Collection +/** + * @template M of object + * @extends BaseResource + */ +abstract class AbstractResource extends BaseResource { use Bootable; use Extendable; diff --git a/framework/core/src/Api/Resource/AccessTokenResource.php b/framework/core/src/Api/Resource/AccessTokenResource.php index 1640f28fa..e6f487550 100644 --- a/framework/core/src/Api/Resource/AccessTokenResource.php +++ b/framework/core/src/Api/Resource/AccessTokenResource.php @@ -24,6 +24,9 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\ModelNotFoundException; use Jenssegers\Agent\Agent; +/** + * @extends AbstractDatabaseResource + */ class AccessTokenResource extends AbstractDatabaseResource { public function __construct( diff --git a/framework/core/src/Api/Resource/Concerns/Bootable.php b/framework/core/src/Api/Resource/Concerns/Bootable.php index 6aea29c4c..e084f7bad 100644 --- a/framework/core/src/Api/Resource/Concerns/Bootable.php +++ b/framework/core/src/Api/Resource/Concerns/Bootable.php @@ -15,9 +15,9 @@ use Illuminate\Contracts\Validation\Factory; trait Bootable { - protected readonly JsonApi $api; - protected readonly Dispatcher $events; - protected readonly Factory $validation; + protected JsonApi $api; + protected Dispatcher $events; + protected Factory $validation; /** * Avoids polluting the constructor of the resource with dependencies. diff --git a/framework/core/src/Api/Resource/Concerns/Extendable.php b/framework/core/src/Api/Resource/Concerns/Extendable.php index 85522bc87..2a9b898da 100644 --- a/framework/core/src/Api/Resource/Concerns/Extendable.php +++ b/framework/core/src/Api/Resource/Concerns/Extendable.php @@ -11,13 +11,13 @@ namespace Flarum\Api\Resource\Concerns; trait Extendable { - private static array $endpointModifiers = []; - private static array $fieldModifiers = []; - private static array $sortModifiers = []; + protected static array $endpointModifiers = []; + protected static array $fieldModifiers = []; + protected static array $sortModifiers = []; - private ?array $cachedEndpoints = null; - private ?array $cachedFields = null; - private ?array $cachedSorts = null; + protected ?array $cachedEndpoints = null; + protected ?array $cachedFields = null; + protected ?array $cachedSorts = null; public static function mutateEndpoints(callable $modifier): void { diff --git a/framework/core/src/Api/Resource/Contracts/Attachable.php b/framework/core/src/Api/Resource/Contracts/Attachable.php deleted file mode 100644 index c446e214c..000000000 --- a/framework/core/src/Api/Resource/Contracts/Attachable.php +++ /dev/null @@ -1,17 +0,0 @@ - + */ class DiscussionResource extends AbstractDatabaseResource { public function __construct( @@ -213,6 +216,9 @@ class DiscussionResource extends AbstractDatabaseResource return fn () => $discussion->posts->all(); } + /** @var Endpoint\Show $endpoint */ + $endpoint = $context->endpoint; + $actor = $context->getActor(); $limit = PostResource::$defaultLimit; @@ -221,7 +227,7 @@ class DiscussionResource extends AbstractDatabaseResource $offset = $this->posts->getIndexForNumber($discussion->id, $near, $actor); $offset = max(0, $offset - $limit / 2); } else { - $offset = $context->endpoint->extractOffsetValue($context, $context->endpoint->defaultExtracts($context)); + $offset = $endpoint->extractOffsetValue($context, $endpoint->defaultExtracts($context)); } $posts = $discussion->posts() @@ -304,14 +310,12 @@ class DiscussionResource extends AbstractDatabaseResource $model->newQuery()->getConnection()->transaction(function () use ($model, $context) { $model->save(); - /** - * @var JsonApi $api - * @var Post $post - */ + /** @var JsonApi $api */ $api = $context->api; // Now that the discussion has been created, we can add the first post. // We will do this by running the PostReply command. + /** @var Post $post */ $post = $api->forResource(PostResource::class) ->forEndpoint('create') ->withRequest($context->request) diff --git a/framework/core/src/Api/Resource/ExtensionReadmeResource.php b/framework/core/src/Api/Resource/ExtensionReadmeResource.php index 69359b89c..580eb88d5 100644 --- a/framework/core/src/Api/Resource/ExtensionReadmeResource.php +++ b/framework/core/src/Api/Resource/ExtensionReadmeResource.php @@ -10,14 +10,16 @@ namespace Flarum\Api\Resource; use Flarum\Api\Endpoint; -use Flarum\Api\Resource\Contracts\Findable; use Flarum\Api\Schema; use Flarum\Extension\Extension; use Flarum\Extension\ExtensionManager; use Tobyz\JsonApiServer\Context; +use Tobyz\JsonApiServer\Resource\Findable; /** * @todo: change to a simple ExtensionResource with readme field. + * + * @extends AbstractResource */ class ExtensionReadmeResource extends AbstractResource implements Findable { diff --git a/framework/core/src/Api/Resource/ForumResource.php b/framework/core/src/Api/Resource/ForumResource.php index df3b137d2..2c508d410 100644 --- a/framework/core/src/Api/Resource/ForumResource.php +++ b/framework/core/src/Api/Resource/ForumResource.php @@ -11,19 +11,26 @@ namespace Flarum\Api\Resource; use Flarum\Api\Context; use Flarum\Api\Endpoint; -use Flarum\Api\Resource\Contracts\Findable; use Flarum\Api\Schema; use Flarum\Foundation\Application; use Flarum\Foundation\Config; use Flarum\Group\Group; use Flarum\Http\UrlGenerator; use Flarum\Settings\SettingsRepositoryInterface; +use Illuminate\Contracts\Filesystem\Cloud; use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Contracts\Filesystem\Filesystem; use stdClass; +use Tobyz\JsonApiServer\Resource\Findable; +/** + * @extends AbstractResource + */ class ForumResource extends AbstractResource implements Findable { + /** + * @var Filesystem&Cloud + */ protected Filesystem $assetsFilesystem; public function __construct( diff --git a/framework/core/src/Api/Resource/GroupResource.php b/framework/core/src/Api/Resource/GroupResource.php index a0c82cfe2..d73706353 100644 --- a/framework/core/src/Api/Resource/GroupResource.php +++ b/framework/core/src/Api/Resource/GroupResource.php @@ -20,6 +20,9 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Arr; use Tobyz\JsonApiServer\Context; +/** + * @extends AbstractDatabaseResource + */ class GroupResource extends AbstractDatabaseResource { public function __construct( diff --git a/framework/core/src/Api/Resource/MailSettingResource.php b/framework/core/src/Api/Resource/MailSettingResource.php index b0a7dfa4c..3887bafbc 100644 --- a/framework/core/src/Api/Resource/MailSettingResource.php +++ b/framework/core/src/Api/Resource/MailSettingResource.php @@ -10,7 +10,6 @@ namespace Flarum\Api\Resource; use Flarum\Api\Endpoint; -use Flarum\Api\Resource\Contracts\Findable; use Flarum\Api\Schema; use Flarum\Mail\DriverInterface; use Flarum\Settings\SettingsRepositoryInterface; @@ -18,7 +17,11 @@ use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Validation\Factory; use stdClass; use Tobyz\JsonApiServer\Context; +use Tobyz\JsonApiServer\Resource\Findable; +/** + * @extends AbstractResource + */ class MailSettingResource extends AbstractResource implements Findable { public function __construct( diff --git a/framework/core/src/Api/Resource/NotificationResource.php b/framework/core/src/Api/Resource/NotificationResource.php index 3e4710a12..827200485 100644 --- a/framework/core/src/Api/Resource/NotificationResource.php +++ b/framework/core/src/Api/Resource/NotificationResource.php @@ -16,8 +16,11 @@ use Flarum\Bus\Dispatcher; use Flarum\Notification\Command\ReadNotification; use Flarum\Notification\Notification; use Flarum\Notification\NotificationRepository; -use Tobyz\JsonApiServer\Pagination\Pagination; +use Tobyz\JsonApiServer\Pagination\OffsetPagination; +/** + * @extends AbstractDatabaseResource + */ class NotificationResource extends AbstractDatabaseResource { public function __construct( @@ -39,8 +42,10 @@ class NotificationResource extends AbstractDatabaseResource public function query(\Tobyz\JsonApiServer\Context $context): object { if ($context->listing(self::class)) { - /** @var Pagination $pagination */ - $pagination = ($context->endpoint->paginationResolver)($context); + /** @var Endpoint\Index $endpoint */ + $endpoint = $context->endpoint; + /** @var OffsetPagination $pagination */ + $pagination = ($endpoint->paginationResolver)($context); return $this->notifications->query($context->getActor(), $pagination->limit, $pagination->offset); } diff --git a/framework/core/src/Api/Resource/PostResource.php b/framework/core/src/Api/Resource/PostResource.php index 523f07911..1b1984c1f 100644 --- a/framework/core/src/Api/Resource/PostResource.php +++ b/framework/core/src/Api/Resource/PostResource.php @@ -28,6 +28,9 @@ use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Arr; use Tobyz\JsonApiServer\Exception\BadRequestException; +/** + * @extends AbstractDatabaseResource + */ class PostResource extends AbstractDatabaseResource { public static int $defaultLimit = 20; @@ -187,8 +190,12 @@ class PostResource extends AbstractDatabaseResource } }) ->serialize(function (null|string|array $value, Context $context) { - // Prevent the string type from trying to convert array content (for event posts) to a string. - $context->field->type = null; + /** + * Prevent the string type from trying to convert array content (for event posts) to a string. + * @var Schema\Str $field + */ + $field = $context->field; + $field->type = null; return $value; }), @@ -196,7 +203,7 @@ class PostResource extends AbstractDatabaseResource ->visible(function (Post $post) { return $post instanceof CommentPost; }) - ->get(function (Post $post, Context $context) { + ->get(function (CommentPost $post, Context $context) { try { $rendered = $post->formatContent($context->request); $post->setAttribute('renderFailed', false); diff --git a/framework/core/src/Api/Resource/UserResource.php b/framework/core/src/Api/Resource/UserResource.php index 53359bb10..71746fdd2 100644 --- a/framework/core/src/Api/Resource/UserResource.php +++ b/framework/core/src/Api/Resource/UserResource.php @@ -35,6 +35,9 @@ use Illuminate\Support\Str; use Intervention\Image\ImageManager; use InvalidArgumentException; +/** + * @extends AbstractDatabaseResource + */ class UserResource extends AbstractDatabaseResource { public function __construct( @@ -224,6 +227,7 @@ class UserResource extends AbstractDatabaseResource }) ->set(function (User $user, ?string $value, Context $context) { if ($value) { + /** @var RegistrationToken $token */ $token = RegistrationToken::validOrFail($value); $context->setParam('token', $token); diff --git a/framework/core/src/Discussion/UserState.php b/framework/core/src/Discussion/UserState.php index fc1363c3b..4986c3cab 100644 --- a/framework/core/src/Discussion/UserState.php +++ b/framework/core/src/Discussion/UserState.php @@ -44,11 +44,6 @@ class UserState extends AbstractModel 'last_read_at' => 'datetime' ]; - /** - * The attributes that are mass assignable. - * - * @var string[] - */ protected $fillable = ['last_read_post_number']; /** diff --git a/framework/core/src/Extend/ApiResource.php b/framework/core/src/Extend/ApiResource.php index e6020cb2a..44a0e0c49 100644 --- a/framework/core/src/Extend/ApiResource.php +++ b/framework/core/src/Extend/ApiResource.php @@ -10,11 +10,13 @@ namespace Flarum\Extend; use Flarum\Api\Endpoint\EndpointInterface; -use Flarum\Api\Resource\Contracts\Resource; use Flarum\Extension\Extension; use Flarum\Foundation\ContainerUtil; use Illuminate\Contracts\Container\Container; use ReflectionClass; +use RuntimeException; +use Tobyz\JsonApiServer\Endpoint\Endpoint; +use Tobyz\JsonApiServer\Resource\Resource; use Tobyz\JsonApiServer\Schema\Field\Field; use Tobyz\JsonApiServer\Schema\Sort; @@ -55,7 +57,7 @@ class ApiResource implements ExtenderInterface /** * Remove endpoints from the resource. * - * @param array $endpoints must be an array of class names of the endpoints. + * @param array $endpoints must be an array of names of the endpoints. * @param callable|class-string|null $condition a callable that returns a boolean or a string that represents whether this should be applied. */ public function removeEndpoints(array $endpoints, callable|string $condition = null): self @@ -68,14 +70,13 @@ class ApiResource implements ExtenderInterface /** * Modify an endpoint. * - * @param class-string<\Flarum\Api\Endpoint\EndpointInterface>|array<\Flarum\Api\Endpoint\EndpointInterface> $endpointClass the class name of the endpoint. - * or an array of class names of the endpoints. + * @param string|string[] $endpointNameOrClass the name or class name of the endpoint or an array of so. * @param callable|class-string $mutator a callable that accepts an endpoint and returns the modified endpoint. */ - public function endpoint(string|array $endpointClass, callable|string $mutator): self + public function endpoint(string|array $endpointNameOrClass, callable|string $mutator): self { - foreach ((array) $endpointClass as $endpointClassItem) { - $this->endpoint[$endpointClassItem][] = $mutator; + foreach ((array) $endpointNameOrClass as $item) { + $this->endpoint[$item][] = $mutator; } return $this; @@ -176,39 +177,45 @@ class ApiResource implements ExtenderInterface /** @var class-string<\Flarum\Api\Resource\AbstractResource|\Flarum\Api\Resource\AbstractDatabaseResource> $resourceClass */ $resourceClass = $this->resourceClass; - $resourceClass::mutateEndpoints(function (array $endpoints, Resource $resource) use ($container): array { - foreach ($this->endpoints as $newEndpointsCallback) { - $newEndpointsCallback = ContainerUtil::wrapCallback($newEndpointsCallback, $container); - $endpoints = array_merge($endpoints, $newEndpointsCallback()); - } - foreach ($this->removeEndpoints as $removeEndpointClass) { - [$endpointsToRemove, $condition] = $removeEndpointClass; - - if ($this->isApplicable($condition, $resource, $container)) { - $endpoints = array_filter($endpoints, fn (EndpointInterface $endpoint) => ! in_array($endpoint::class, $endpointsToRemove)); + $resourceClass::mutateEndpoints( + /** + * @var EndpointInterface[] $endpoints + */ + function (array $endpoints, Resource $resource) use ($container): array { + foreach ($this->endpoints as $newEndpointsCallback) { + $newEndpointsCallback = ContainerUtil::wrapCallback($newEndpointsCallback, $container); + $endpoints = array_merge($endpoints, $newEndpointsCallback()); } - } - foreach ($endpoints as $key => $endpoint) { - $endpointClass = $endpoint::class; + foreach ($this->removeEndpoints as $removeEndpointClass) { + [$endpointsToRemove, $condition] = $removeEndpointClass; - if (! empty($this->endpoint[$endpointClass])) { - foreach ($this->endpoint[$endpointClass] as $mutator) { - $mutateEndpoint = ContainerUtil::wrapCallback($mutator, $container); - $endpoint = $mutateEndpoint($endpoint, $resource); - - if (! $endpoint instanceof EndpointInterface) { - throw new \RuntimeException('The endpoint mutator must return an instance of '.EndpointInterface::class); - } + if ($this->isApplicable($condition, $resource, $container)) { + $endpoints = array_filter($endpoints, fn (Endpoint $endpoint) => ! in_array($endpoint->name, $endpointsToRemove)); } } - $endpoints[$key] = $endpoint; - } + foreach ($endpoints as $key => $endpoint) { + $endpointClass = $endpoint::class; - return $endpoints; - }); + if (! empty($this->endpoint[$endpoint->name]) || ! empty($this->endpoint[$endpointClass])) { + foreach (array_merge($this->endpoint[$endpoint->name] ?? [], $this->endpoint[$endpointClass] ?? []) as $mutator) { + $mutateEndpoint = ContainerUtil::wrapCallback($mutator, $container); + $endpoint = $mutateEndpoint($endpoint, $resource); + + if (! $endpoint instanceof EndpointInterface) { + throw new RuntimeException('The endpoint mutator must return an instance of '.EndpointInterface::class); + } + } + } + + $endpoints[$key] = $endpoint; + } + + return $endpoints; + } + ); $resourceClass::mutateFields(function (array $fields, Resource $resource) use ($container): array { foreach ($this->fields as $newFieldsCallback) { @@ -231,7 +238,7 @@ class ApiResource implements ExtenderInterface $field = $mutateField($field); if (! $field instanceof Field) { - throw new \RuntimeException('The field mutator must return an instance of '.Field::class); + throw new RuntimeException('The field mutator must return an instance of '.Field::class); } } } @@ -263,7 +270,7 @@ class ApiResource implements ExtenderInterface $sort = $mutateSort($sort); if (! $sort instanceof Sort) { - throw new \RuntimeException('The sort mutator must return an instance of '.Sort::class); + throw new RuntimeException('The sort mutator must return an instance of '.Sort::class); } } } diff --git a/framework/core/src/Extend/Routes.php b/framework/core/src/Extend/Routes.php index fd1b5950c..5e6e2d3fe 100644 --- a/framework/core/src/Extend/Routes.php +++ b/framework/core/src/Extend/Routes.php @@ -40,7 +40,6 @@ class Routes implements ExtenderInterface * * The handler should accept: * - \Psr\Http\Message\ServerRequestInterface $request - * - \Tobscure\JsonApi\Document $document: If it extends one of the Flarum Api controllers. * * The handler should return: * - \Psr\Http\Message\ResponseInterface $response @@ -64,7 +63,6 @@ class Routes implements ExtenderInterface * * The handler should accept: * - \Psr\Http\Message\ServerRequestInterface $request - * - \Tobscure\JsonApi\Document $document: If it extends one of the Flarum Api controllers. * * The handler should return: * - \Psr\Http\Message\ResponseInterface $response @@ -88,7 +86,6 @@ class Routes implements ExtenderInterface * * The handler should accept: * - \Psr\Http\Message\ServerRequestInterface $request - * - \Tobscure\JsonApi\Document $document: If it extends one of the Flarum Api controllers. * * The handler should return: * - \Psr\Http\Message\ResponseInterface $response @@ -112,7 +109,6 @@ class Routes implements ExtenderInterface * * The handler should accept: * - \Psr\Http\Message\ServerRequestInterface $request - * - \Tobscure\JsonApi\Document $document: If it extends one of the Flarum Api controllers. * * The handler should return: * - \Psr\Http\Message\ResponseInterface $response @@ -136,7 +132,6 @@ class Routes implements ExtenderInterface * * The handler should accept: * - \Psr\Http\Message\ServerRequestInterface $request - * - \Tobscure\JsonApi\Document $document: If it extends one of the Flarum Api controllers. * * The handler should return: * - \Psr\Http\Message\ResponseInterface $response diff --git a/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/JsonApiExceptionHandler.php b/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/JsonApiExceptionHandler.php index a2ca666f6..f9a724c12 100644 --- a/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/JsonApiExceptionHandler.php +++ b/framework/core/src/Foundation/ErrorHandling/ExceptionHandler/JsonApiExceptionHandler.php @@ -20,7 +20,7 @@ class JsonApiExceptionHandler return (new HandledError( $e, 'validation_error', - $e->getJsonApiStatus() + intval($e->getJsonApiStatus()) ))->withDetails($e->getJsonApiErrors()); } } diff --git a/framework/core/src/Http/RequestUtil.php b/framework/core/src/Http/RequestUtil.php index 3a98f854e..fc394c6b4 100644 --- a/framework/core/src/Http/RequestUtil.php +++ b/framework/core/src/Http/RequestUtil.php @@ -78,7 +78,7 @@ class RequestUtil { $limit = $request->getQueryParams()['page']['limit'] ?? ''; - if (is_null($limit) || ! filled($limit)) { + if (! filled($limit)) { $limit = $defaultLimit; } diff --git a/framework/core/src/Notification/NotificationRepository.php b/framework/core/src/Notification/NotificationRepository.php index 9fdacb371..56ecb7845 100644 --- a/framework/core/src/Notification/NotificationRepository.php +++ b/framework/core/src/Notification/NotificationRepository.php @@ -24,6 +24,9 @@ class NotificationRepository return $this->query($user, $limit, $offset)->get(); } + /** + * @return Builder + */ public function query(User $user, ?int $limit = null, int $offset = 0): Builder { $primaries = Notification::query() diff --git a/php-packages/phpstan/extension.neon b/php-packages/phpstan/extension.neon index 7de009331..92b95be28 100644 --- a/php-packages/phpstan/extension.neon +++ b/php-packages/phpstan/extension.neon @@ -16,6 +16,9 @@ parameters: - stubs/Illuminate/Contracts/Filesystem/Cloud.stub - stubs/Illuminate/Contracts/Filesystem/Filesystem.stub + # We know for a fact the JsonApi object used internally is always the Flarum one. + - stubs/Tobyz/JsonApiServer/JsonApi.stub + services: - class: Flarum\PHPStan\Relations\ModelRelationsExtension diff --git a/php-packages/phpstan/phpstan-baseline.neon b/php-packages/phpstan/phpstan-baseline.neon index be8117205..19c3148b3 100644 --- a/php-packages/phpstan/phpstan-baseline.neon +++ b/php-packages/phpstan/phpstan-baseline.neon @@ -30,3 +30,13 @@ parameters: # ignore this error, so we have to ignore it globally. - message: '#^Parameter \#[0-9]+ \$[A-z0-9_]+ of method Flarum\Extend\[A-z0-9_:\\()]+ expects \(?callable\([A-z0-9_,|\\: ()-]+\)\)?, (callable|Closure)\([A-z0-9_,|\\: ()-]+\) given\.$#' reportUnmatched: false + + # PHPStan suddenly doesn't recognize callables can be function names? + - message: '#^Parameter \#[0-9]+ \$[A-z0-9_]+ of function [A-z0-9_:\\()]+ expects \(?callable\([A-z0-9_,|\\: ()-]+, ''[A-z0-9_:\\()]+'' given\.$#' + reportUnmatched: false + + # Not if we're using our own static make method. + - message: '#^Called ''Model\:\:make\(\)'' which performs unnecessary work, use ''new Model\(\)''\.$#' + + # This assumes that the phpdoc telling it it's not nullable is correct, that's not the case for internal Laravel typings. + - message: '#^Property [A-z0-9-_:$,\\]+ \([A-z]+\) on left side of \?\? is not nullable\.$#' diff --git a/php-packages/phpstan/stubs/Tobyz/JsonApiServer/JsonApi.stub b/php-packages/phpstan/stubs/Tobyz/JsonApiServer/JsonApi.stub new file mode 100644 index 000000000..c2d37c69f --- /dev/null +++ b/php-packages/phpstan/stubs/Tobyz/JsonApiServer/JsonApi.stub @@ -0,0 +1,11 @@ +