diff --git a/framework/core/src/Api/ApiServiceProvider.php b/framework/core/src/Api/ApiServiceProvider.php index 69646ad4a..4dd1ae8b4 100644 --- a/framework/core/src/Api/ApiServiceProvider.php +++ b/framework/core/src/Api/ApiServiceProvider.php @@ -23,6 +23,7 @@ use Flarum\Http\RouteHandlerFactory; use Flarum\Http\UrlGenerator; use Illuminate\Contracts\Container\Container; use Laminas\Stratigility\MiddlewarePipe; +use ReflectionClass; class ApiServiceProvider extends AbstractServiceProvider { @@ -48,11 +49,12 @@ class ApiServiceProvider extends AbstractServiceProvider $resources = $this->container->make('flarum.api.resources'); $api = new JsonApi('/'); + $api->container($container); foreach ($resources as $resourceClass) { /** @var \Flarum\Api\Resource\AbstractResource|\Flarum\Api\Resource\AbstractDatabaseResource $resource */ - $resource = new $resourceClass; - $resource->boot($container); + $resource = $container->make($resourceClass); + $resource->boot($api); $api->resource($resource); } @@ -61,9 +63,9 @@ class ApiServiceProvider extends AbstractServiceProvider $this->container->alias('flarum.api.resource_handler', JsonApi::class); - $this->container->singleton('flarum.api.routes', function () { + $this->container->singleton('flarum.api.routes', function (Container $container) { $routes = new RouteCollection; - $this->populateRoutes($routes); + $this->populateRoutes($routes, $container); return $routes; }); @@ -158,10 +160,10 @@ class ApiServiceProvider extends AbstractServiceProvider AbstractSerializer::setContainer($container); } - protected function populateRoutes(RouteCollection $routes): void + protected function populateRoutes(RouteCollection $routes, Container $container): void { /** @var RouteHandlerFactory $factory */ - $factory = $this->container->make(RouteHandlerFactory::class); + $factory = $container->make(RouteHandlerFactory::class); $callback = include __DIR__.'/routes.php'; $callback($routes, $factory); @@ -169,16 +171,30 @@ class ApiServiceProvider extends AbstractServiceProvider $resources = $this->container->make('flarum.api.resources'); foreach ($resources as $resourceClass) { - /** @var \Flarum\Api\Resource\AbstractResource|\Flarum\Api\Resource\AbstractDatabaseResource $resource */ - $resource = new $resourceClass; - /** @var \Flarum\Api\Endpoint\Endpoint[] $endpoints */ - $endpoints = $resource->endpoints(); + /** + * This is an empty shell instance, + * we only need it to get the endpoint routes and types. + * + * We avoid dependency injection here to avoid early resolution. + * + * @var \Flarum\Api\Resource\AbstractResource|\Flarum\Api\Resource\AbstractDatabaseResource $resource + */ + $resource = (new ReflectionClass($resourceClass))->newInstanceWithoutConstructor(); + $type = $resource->type(); + /** + * None of the injected dependencies should be directly used within + * the `endpoints` method. Encourage using callbacks. + * + * @var \Flarum\Api\Endpoint\Endpoint[] $endpoints + */ + $endpoints = $resource->endpoints(); + foreach ($endpoints as $endpoint) { $route = $endpoint->route(); - $routes->addRoute($route->method, rtrim("/$type$route->path", '/'), "$type.$route->name", $factory->toApiResource($resourceClass, $endpoint::class)); + $routes->addRoute($route->method, rtrim("/$type$route->path", '/'), "$type.$route->name", $factory->toApiResource($resource::class, $endpoint::class)); } } } diff --git a/framework/core/src/Api/Context.php b/framework/core/src/Api/Context.php index 6139e0ed4..45b21a50f 100644 --- a/framework/core/src/Api/Context.php +++ b/framework/core/src/Api/Context.php @@ -5,6 +5,7 @@ namespace Flarum\Api; use Flarum\Http\RequestUtil; use Flarum\Search\SearchResults; use Flarum\User\User; +use Illuminate\Contracts\Container\Container; use Tobyz\JsonApiServer\Context as BaseContext; class Context extends BaseContext diff --git a/framework/core/src/Api/Controller/UpdateDiscussionController.php b/framework/core/src/Api/Controller/UpdateDiscussionController.php deleted file mode 100644 index 24516159c..000000000 --- a/framework/core/src/Api/Controller/UpdateDiscussionController.php +++ /dev/null @@ -1,74 +0,0 @@ -getQueryParams(), 'id'); - $data = Arr::get($request->getParsedBody(), 'data', []); - - /** @var Discussion $discussion */ - $discussion = $this->bus->dispatch( - new EditDiscussion($discussionId, $actor, $data) - ); - - // TODO: Refactor the ReadDiscussion (state) command into EditDiscussion? - // That's what extensions will do anyway. - if ($readNumber = Arr::get($data, 'attributes.lastReadPostNumber')) { - $state = $this->bus->dispatch( - new ReadDiscussion($discussionId, $actor, $readNumber) - ); - - $discussion = $state->discussion; - } - - if ($posts = $discussion->getModifiedPosts()) { - /** @var Collection $posts */ - $posts = (new Collection($posts))->load('discussion', 'user'); - $discussionPosts = $discussion->posts()->whereVisibleTo($actor)->oldest()->pluck('id')->all(); - - foreach ($discussionPosts as &$id) { - foreach ($posts as $post) { - if ($id == $post->id) { - $id = $post; - } - } - } - - $discussion->setRelation('posts', $discussionPosts); - - $this->include = array_merge($this->include, ['posts', 'posts.discussion', 'posts.user']); - } - - return $discussion; - } -} diff --git a/framework/core/src/Api/Endpoint/Index.php b/framework/core/src/Api/Endpoint/Index.php index 96a91309b..fc74d99ba 100644 --- a/framework/core/src/Api/Endpoint/Index.php +++ b/framework/core/src/Api/Endpoint/Index.php @@ -71,7 +71,7 @@ class Index extends BaseIndex implements Endpoint // This model has a searcher API, so we'll use that instead of the default. // The searcher API allows swapping the default search engine for a custom one. - $search = resolve(SearchManager::class); + $search = $context->api->getContainer()->make(SearchManager::class); $modelClass = $query->getModel()::class; if ($query instanceof Builder && $search->searchable($modelClass)) { @@ -86,8 +86,7 @@ class Index extends BaseIndex implements Endpoint $sortIsDefault = ! $context->queryParam('sort'); - // @todo: resources and endpoints have no room for dependency injection - $results = resolve(SearchManager::class)->query( + $results = $search->query( $modelClass, new SearchCriteria($actor, $filters, $limit, $offset, $sort, $sortIsDefault), ); diff --git a/framework/core/src/Api/JsonApi.php b/framework/core/src/Api/JsonApi.php index cb97c271c..88967d7bb 100644 --- a/framework/core/src/Api/JsonApi.php +++ b/framework/core/src/Api/JsonApi.php @@ -6,20 +6,22 @@ use Flarum\Api\Endpoint\Endpoint; use Flarum\Api\Endpoint\EndpointRoute; use Flarum\Api\Resource\AbstractDatabaseResource; use Flarum\Http\RequestUtil; +use Illuminate\Contracts\Container\Container; use Laminas\Diactoros\ServerRequestFactory; use Laminas\Diactoros\Uri; use Psr\Http\Message\ResponseInterface as Response; -use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface as Request; use Tobyz\JsonApiServer\Exception\BadRequestException; use Tobyz\JsonApiServer\JsonApi as BaseJsonApi; use Tobyz\JsonApiServer\Resource\Collection; +use Tobyz\JsonApiServer\Resource\Resource; class JsonApi extends BaseJsonApi { protected string $resourceClass; protected string $endpoint; protected ?Request $baseRequest = null; + protected ?Container $container = null; public function forResource(string $resourceClass): self { @@ -41,7 +43,7 @@ class JsonApi extends BaseJsonApi throw new BadRequestException('No resource or endpoint specified'); } - $collection = $this->getCollection((new $this->resourceClass)->type()); + $collection = $this->getCollection($this->resourceClass); return (new Context($this, $request)) ->withCollection($collection) @@ -85,6 +87,8 @@ class JsonApi extends BaseJsonApi $request = RequestUtil::withActor($request, $options['actor']); } + $resource = $this->getCollection($this->resourceClass); + $request = $request ->withMethod($route->method) ->withUri(new Uri($route->path)) @@ -93,7 +97,9 @@ class JsonApi extends BaseJsonApi 'data' => [ ...($request->getParsedBody()['data'] ?? []), ...($body['data'] ?? []), - 'type' => (new $this->resourceClass)->type(), + 'type' => $resource instanceof Resource + ? $resource->type() + : $resource->name(), ], ]); @@ -136,4 +142,16 @@ class JsonApi extends BaseJsonApi { return array_values(array_unique(array_map(fn ($modelClass) => $this->typeForModel($modelClass), $modelClasses))); } + + public function container(Container $container): static + { + $this->container = $container; + + return $this; + } + + public function getContainer(): ?Container + { + return $this->container; + } } diff --git a/framework/core/src/Api/Resource/AbstractDatabaseResource.php b/framework/core/src/Api/Resource/AbstractDatabaseResource.php index 27c117839..5cdb6411f 100644 --- a/framework/core/src/Api/Resource/AbstractDatabaseResource.php +++ b/framework/core/src/Api/Resource/AbstractDatabaseResource.php @@ -34,7 +34,6 @@ abstract class AbstractDatabaseResource extends BaseResource implements use DispatchEventsTrait { dispatchEventsFor as traitDispatchEventsFor; } - use ResolvesValidationFactory; abstract public function model(): string; @@ -147,7 +146,7 @@ abstract class AbstractDatabaseResource extends BaseResource implements $savingEvent = $this->newSavingEvent($context, Arr::get($context->body(), 'data', [])); if ($savingEvent) { - $this->container->make(Dispatcher::class)->dispatch($savingEvent); + $this->events->dispatch($savingEvent); $dirtyAfterEvent = $context->model->getDirty(); diff --git a/framework/core/src/Api/Resource/AbstractResource.php b/framework/core/src/Api/Resource/AbstractResource.php index 6a2c4886a..a99b6605d 100644 --- a/framework/core/src/Api/Resource/AbstractResource.php +++ b/framework/core/src/Api/Resource/AbstractResource.php @@ -9,5 +9,4 @@ use Tobyz\JsonApiServer\Resource\AbstractResource as BaseResource; abstract class AbstractResource extends BaseResource { use Bootable; - use ResolvesValidationFactory; } diff --git a/framework/core/src/Api/Resource/AccessTokenResource.php b/framework/core/src/Api/Resource/AccessTokenResource.php index d8bbe7b99..30827f136 100644 --- a/framework/core/src/Api/Resource/AccessTokenResource.php +++ b/framework/core/src/Api/Resource/AccessTokenResource.php @@ -19,6 +19,11 @@ use Jenssegers\Agent\Agent; class AccessTokenResource extends AbstractDatabaseResource { + public function __construct( + protected TranslatorInterface $translator + ) { + } + public function type(): string { return 'access-tokens'; @@ -84,12 +89,10 @@ class AccessTokenResource extends AbstractDatabaseResource Schema\Str::make('lastIpAddress'), Schema\Str::make('device') ->get(function (AccessToken $token) { - $translator = resolve(TranslatorInterface::class); - $agent = new Agent(); $agent->setUserAgent($token->last_user_agent); - return $translator->trans('core.forum.security.browser_on_operating_system', [ + return $this->translator->trans('core.forum.security.browser_on_operating_system', [ 'browser' => $agent->browser(), 'os' => $agent->platform(), ]); diff --git a/framework/core/src/Api/Resource/Concerns/Bootable.php b/framework/core/src/Api/Resource/Concerns/Bootable.php index bb3503f79..795958085 100644 --- a/framework/core/src/Api/Resource/Concerns/Bootable.php +++ b/framework/core/src/Api/Resource/Concerns/Bootable.php @@ -2,17 +2,32 @@ namespace Flarum\Api\Resource\Concerns; +use Flarum\Api\JsonApi; use Illuminate\Contracts\Container\Container; use Illuminate\Contracts\Events\Dispatcher; +use Illuminate\Contracts\Validation\Factory; trait Bootable { - protected readonly Container $container; + protected readonly JsonApi $api; protected readonly Dispatcher $events; + protected readonly Factory $validation; - public function boot(Container $container): void + /** + * Avoids polluting the constructor of the resource with dependencies. + */ + public function boot(JsonApi $api): void { - $this->container = $container; - $this->events = $container->make(Dispatcher::class); + $this->api = $api; + $this->events = $api->getContainer()->make(Dispatcher::class); + $this->validation = $api->getContainer()->make(Factory::class); + } + + /** + * Called by the JSON:API server package to resolve the validation factory. + */ + public function validationFactory(): Factory + { + return $this->validation; } } diff --git a/framework/core/src/Api/Resource/Concerns/ResolvesValidationFactory.php b/framework/core/src/Api/Resource/Concerns/ResolvesValidationFactory.php deleted file mode 100644 index 25642d842..000000000 --- a/framework/core/src/Api/Resource/Concerns/ResolvesValidationFactory.php +++ /dev/null @@ -1,16 +0,0 @@ -getActor(); if (Arr::get($context->request->getQueryParams(), 'bySlug', false)) { - $discussion = $slugManager->forResource(Discussion::class)->fromSlug($id, $actor); + $discussion = $this->slugManager->forResource(Discussion::class)->fromSlug($id, $actor); } else { $discussion = $this->query($context)->findOrFail($id); } @@ -113,7 +119,7 @@ class DiscussionResource extends AbstractDatabaseResource ->set(fn () => null), Schema\Str::make('slug') ->get(function (Discussion $discussion) { - return resolve(SlugManager::class)->forResource(Discussion::class)->toSlug($discussion); + return $this->slugManager->forResource(Discussion::class)->toSlug($discussion); }), Schema\Integer::make('commentCount'), Schema\Integer::make('participantCount'), @@ -167,7 +173,7 @@ class DiscussionResource extends AbstractDatabaseResource ->set(function (Discussion $discussion, int $value, Context $context) { if ($readNumber = Arr::get($context->body(), 'data.attributes.lastReadPostNumber')) { $discussion->afterSave(function (Discussion $discussion) use ($readNumber, $context) { - resolve(Dispatcher::class)->dispatch( + $this->bus->dispatch( new ReadDiscussion($discussion->id, $context->getActor(), $readNumber) ); }); @@ -196,7 +202,7 @@ class DiscussionResource extends AbstractDatabaseResource $limit = $context->endpoint->extractLimitValue($context, $context->endpoint->defaultExtracts($context)); if (($near = Arr::get($context->request->getQueryParams(), 'page.near')) > 1) { - $offset = resolve(PostRepository::class)->getIndexForNumber($discussion->id, $near, $actor); + $offset = $this->posts->getIndexForNumber($discussion->id, $near, $actor); $offset = max(0, $offset - $limit / 2); } else { $offset = $context->endpoint->extractOffsetValue($context, $context->endpoint->defaultExtracts($context)); @@ -263,7 +269,7 @@ class DiscussionResource extends AbstractDatabaseResource $actor = $context->getActor(); if ($actor->exists) { - resolve(Dispatcher::class)->dispatch( + $this->bus->dispatch( new ReadDiscussion($model->id, $actor, 1) ); } diff --git a/framework/core/src/Api/Resource/ForumResource.php b/framework/core/src/Api/Resource/ForumResource.php index 76b755e46..c492c78f4 100644 --- a/framework/core/src/Api/Resource/ForumResource.php +++ b/framework/core/src/Api/Resource/ForumResource.php @@ -12,10 +12,22 @@ use Flarum\Group\Group; use Flarum\Http\UrlGenerator; use Flarum\Settings\SettingsRepositoryInterface; use Illuminate\Contracts\Filesystem\Factory; +use Illuminate\Contracts\Filesystem\Filesystem; use stdClass; class ForumResource extends AbstractResource implements Findable { + protected Filesystem $assetsFilesystem; + + public function __construct( + protected UrlGenerator $url, + protected SettingsRepositoryInterface $settings, + protected Config $config, + Factory $filesystemFactory + ) { + $this->assetsFilesystem = $filesystemFactory->disk('flarum-assets'); + } + public function type(): string { return 'forums'; @@ -42,21 +54,16 @@ class ForumResource extends AbstractResource implements Findable public function fields(): array { - $url = resolve(UrlGenerator::class); - $settings = resolve(SettingsRepositoryInterface::class); - $config = resolve(Config::class); - $assetsFilesystem = resolve(Factory::class)->disk('flarum-assets'); - - $forumUrl = $url->to('forum')->base(); + $forumUrl = $this->url->to('forum')->base(); $path = parse_url($forumUrl, PHP_URL_PATH) ?: ''; return [ Schema\Str::make('title') - ->get(fn () => $settings->get('forum_title')), + ->get(fn () => $this->settings->get('forum_title')), Schema\Str::make('description') - ->get(fn () => $settings->get('forum_description')), + ->get(fn () => $this->settings->get('forum_description')), Schema\Boolean::make('showLanguageSelector') - ->get(fn () => $settings->get('show_language_selector', true)), + ->get(fn () => $this->settings->get('show_language_selector', true)), Schema\Str::make('baseUrl') ->get(fn () => $forumUrl), Schema\Str::make('basePath') @@ -64,29 +71,29 @@ class ForumResource extends AbstractResource implements Findable Schema\Str::make('baseOrigin') ->get(fn () => substr($forumUrl, 0, strlen($forumUrl) - strlen($path))), Schema\Str::make('debug') - ->get(fn () => $config->inDebugMode()), + ->get(fn () => $this->config->inDebugMode()), Schema\Str::make('apiUrl') - ->get(fn () => $url->to('api')->base()), + ->get(fn () => $this->url->to('api')->base()), Schema\Str::make('welcomeTitle') - ->get(fn () => $settings->get('welcome_title')), + ->get(fn () => $this->settings->get('welcome_title')), Schema\Str::make('welcomeMessage') - ->get(fn () => $settings->get('welcome_message')), + ->get(fn () => $this->settings->get('welcome_message')), Schema\Str::make('themePrimaryColor') - ->get(fn () => $settings->get('theme_primary_color')), + ->get(fn () => $this->settings->get('theme_primary_color')), Schema\Str::make('themeSecondaryColor') - ->get(fn () => $settings->get('theme_secondary_color')), + ->get(fn () => $this->settings->get('theme_secondary_color')), Schema\Str::make('logoUrl') ->get(fn () => $this->getLogoUrl()), Schema\Str::make('faviconUrl') ->get(fn () => $this->getFaviconUrl()), Schema\Str::make('headerHtml') - ->get(fn () => $settings->get('custom_header')), + ->get(fn () => $this->settings->get('custom_header')), Schema\Str::make('footerHtml') - ->get(fn () => $settings->get('custom_footer')), + ->get(fn () => $this->settings->get('custom_footer')), Schema\Boolean::make('allowSignUp') - ->get(fn () => $settings->get('allow_sign_up')), + ->get(fn () => $this->settings->get('allow_sign_up')), Schema\Str::make('defaultRoute') - ->get(fn () => $settings->get('default_route')), + ->get(fn () => $this->settings->get('default_route')), Schema\Boolean::make('canViewForum') ->get(fn ($model, Context $context) => $context->getActor()->can('viewForum')), Schema\Boolean::make('canStartDiscussion') @@ -100,13 +107,13 @@ class ForumResource extends AbstractResource implements Findable Schema\Boolean::make('canEditUserCredentials') ->get(fn ($model, Context $context) => $context->getActor()->hasPermission('user.editCredentials')), Schema\Str::make('assetsBaseUrl') - ->get(fn () => rtrim($assetsFilesystem->url(''), '/')), + ->get(fn () => rtrim($this->assetsFilesystem->url(''), '/')), Schema\Str::make('jsChunksBaseUrl') - ->get(fn () => $assetsFilesystem->url('js')), + ->get(fn () => $this->assetsFilesystem->url('js')), Schema\Str::make('adminUrl') ->visible(fn ($model, Context $context) => $context->getActor()->can('administrate')) - ->get(fn () => $url->to('admin')->base()), + ->get(fn () => $this->url->to('admin')->base()), Schema\Str::make('version') ->visible(fn ($model, Context $context) => $context->getActor()->can('administrate')) ->get(fn () => Application::VERSION), @@ -123,20 +130,20 @@ class ForumResource extends AbstractResource implements Findable protected function getLogoUrl(): ?string { - $logoPath = resolve(SettingsRepositoryInterface::class)->get('logo_path'); + $logoPath = $this->settings->get('logo_path'); return $logoPath ? $this->getAssetUrl($logoPath) : null; } protected function getFaviconUrl(): ?string { - $faviconPath = resolve(SettingsRepositoryInterface::class)->get('favicon_path'); + $faviconPath = $this->settings->get('favicon_path'); return $faviconPath ? $this->getAssetUrl($faviconPath) : null; } public function getAssetUrl(string $assetPath): string { - return resolve(Factory::class)->disk('flarum-assets')->url($assetPath); + return $this->assetsFilesystem->url($assetPath); } } diff --git a/framework/core/src/Api/Resource/GroupResource.php b/framework/core/src/Api/Resource/GroupResource.php index 25b349e74..fbf67e29e 100644 --- a/framework/core/src/Api/Resource/GroupResource.php +++ b/framework/core/src/Api/Resource/GroupResource.php @@ -15,6 +15,11 @@ use Tobyz\JsonApiServer\Laravel\Sort\SortColumn; class GroupResource extends AbstractDatabaseResource { + public function __construct( + protected TranslatorInterface $translator + ) { + } + public function type(): string { return 'groups'; @@ -92,7 +97,7 @@ class GroupResource extends AbstractDatabaseResource private function translateGroupName(string $name): string { - $translation = resolve(TranslatorInterface::class)->trans($key = 'core.group.'.strtolower($name)); + $translation = $this->translator->trans($key = 'core.group.'.strtolower($name)); if ($translation !== $key) { return $translation; diff --git a/framework/core/src/Api/Resource/NotificationResource.php b/framework/core/src/Api/Resource/NotificationResource.php index 7da404878..cfa8a66fc 100644 --- a/framework/core/src/Api/Resource/NotificationResource.php +++ b/framework/core/src/Api/Resource/NotificationResource.php @@ -14,6 +14,12 @@ use Tobyz\JsonApiServer\Pagination\Pagination; class NotificationResource extends AbstractDatabaseResource { + public function __construct( + protected Dispatcher $bus, + protected NotificationRepository $notifications, + ) { + } + public function type(): string { return 'notifications'; @@ -30,7 +36,7 @@ class NotificationResource extends AbstractDatabaseResource /** @var Pagination $pagination */ $pagination = ($context->endpoint->paginationResolver)($context); - return resolve(NotificationRepository::class)->query($context->getActor(), $pagination->limit, $pagination->offset); + return $this->notifications->query($context->getActor(), $pagination->limit, $pagination->offset); } return parent::query($context); @@ -57,7 +63,7 @@ class NotificationResource extends AbstractDatabaseResource public function fields(): array { - $subjectTypes = resolve(JsonApi::class)->typesForModels( + $subjectTypes = $this->api->typesForModels( (new Notification())->getSubjectModels() ); @@ -71,7 +77,7 @@ class NotificationResource extends AbstractDatabaseResource ->writable() ->get(fn (Notification $notification) => (bool) $notification->read_at) ->set(function (Notification $notification, Context $context) { - resolve(Dispatcher::class)->dispatch( + $this->bus->dispatch( new ReadNotification($notification->id, $context->getActor()) ); }), diff --git a/framework/core/src/Api/Resource/PostResource.php b/framework/core/src/Api/Resource/PostResource.php index c25a2a148..108aa1852 100644 --- a/framework/core/src/Api/Resource/PostResource.php +++ b/framework/core/src/Api/Resource/PostResource.php @@ -23,6 +23,14 @@ use Tobyz\JsonApiServer\Laravel\Sort\SortColumn; class PostResource extends AbstractDatabaseResource { + public function __construct( + protected PostRepository $posts, + protected TranslatorInterface $translator, + protected LogReporter $log, + protected Dispatcher $bus + ) { + } + public function type(): string { return 'posts'; @@ -115,7 +123,7 @@ class PostResource extends AbstractDatabaseResource } $limit = $defaultExtracts['limit']; - $offset = resolve(PostRepository::class)->getIndexForNumber((int) $filter['discussion'], $near, $context->getActor()); + $offset = $this->posts->getIndexForNumber((int) $filter['discussion'], $near, $context->getActor()); return max(0, $offset - $limit / 2); } @@ -184,8 +192,8 @@ class PostResource extends AbstractDatabaseResource $rendered = $post->formatContent($context->request); $post->setAttribute('renderFailed', false); } catch (\Exception $e) { - $rendered = resolve(TranslatorInterface::class)->trans('core.lib.error.render_failed_message'); - resolve(LogReporter::class)->report($e); + $rendered = $this->translator->trans('core.lib.error.render_failed_message'); + $this->log->report($e); $post->setAttribute('renderFailed', true); } @@ -258,7 +266,7 @@ class PostResource extends AbstractDatabaseResource // in the discussion; thus, we will mark the discussion as read if // they are logged in. if ($actor->exists) { - resolve(Dispatcher::class)->dispatch( + $this->bus->dispatch( new ReadDiscussion($model->discussion_id, $actor, $model->number) ); } diff --git a/framework/core/src/Api/Resource/UserResource.php b/framework/core/src/Api/Resource/UserResource.php index 971108536..52dac17f7 100644 --- a/framework/core/src/Api/Resource/UserResource.php +++ b/framework/core/src/Api/Resource/UserResource.php @@ -9,6 +9,7 @@ use Flarum\Foundation\ValidationException; use Flarum\Http\SlugManager; use Flarum\Locale\TranslatorInterface; use Flarum\Settings\SettingsRepositoryInterface; +use Flarum\User\AvatarUploader; use Flarum\User\Event\Deleting; use Flarum\User\Event\GroupsChanged; use Flarum\User\Event\RegisteringFromProvider; @@ -19,11 +20,21 @@ use Flarum\User\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Arr; use Illuminate\Support\Str; +use Intervention\Image\ImageManager; use InvalidArgumentException; use Tobyz\JsonApiServer\Laravel\Sort\SortColumn; class UserResource extends AbstractDatabaseResource { + public function __construct( + protected TranslatorInterface $translator, + protected SlugManager $slugManager, + protected SettingsRepositoryInterface $settings, + protected ImageManager $imageManager, + protected AvatarUploader $avatarUploader + ) { + } + public function type(): string { return 'users'; @@ -41,11 +52,10 @@ class UserResource extends AbstractDatabaseResource public function find(string $id, \Tobyz\JsonApiServer\Context $context): ?object { - $slugManager = resolve(SlugManager::class); $actor = $context->getActor(); if (Arr::get($context->request->getQueryParams(), 'bySlug', false)) { - $user = $slugManager->forResource(User::class)->fromSlug($id, $actor); + $user = $this->slugManager->forResource(User::class)->fromSlug($id, $actor); } else { $user = $this->query($context)->findOrFail($id); } @@ -58,9 +68,7 @@ class UserResource extends AbstractDatabaseResource return [ Endpoint\Create::make() ->visible(function (Context $context) { - $settings = resolve(SettingsRepositoryInterface::class); - - if (! $settings->get('allow_sign_up')) { + if (! $this->settings->get('allow_sign_up')) { return $context->getActor()->isAdmin(); } @@ -101,7 +109,7 @@ class UserResource extends AbstractDatabaseResource public function fields(): array { - $translator = resolve(TranslatorInterface::class); + $translator = $this->translator; return [ Schema\Str::make('username') @@ -203,7 +211,7 @@ class UserResource extends AbstractDatabaseResource Schema\Str::make('avatarUrl'), Schema\Str::make('slug') ->get(function (User $user) { - return resolve(SlugManager::class)->forResource(User::class)->toSlug($user); + return $this->slugManager->forResource(User::class)->toSlug($user); }), Schema\DateTime::make('joinTime') ->property('joined_at'), @@ -363,12 +371,7 @@ class UserResource extends AbstractDatabaseResource */ private function uploadAvatarFromUrl(User $user, string $url): void { - // @todo: constructor dependency injection - $this->validator = resolve(\Illuminate\Contracts\Validation\Factory::class); - $this->imageManager = resolve(\Intervention\Image\ImageManager::class); - $this->avatarUploader = resolve(\Flarum\User\AvatarUploader::class); - - $urlValidator = $this->validator->make(compact('url'), [ + $urlValidator = $this->validation->make(compact('url'), [ 'url' => 'required|active_url', ]); diff --git a/framework/core/src/Http/RouteHandlerFactory.php b/framework/core/src/Http/RouteHandlerFactory.php index 10ca8b7b9..2abe89096 100644 --- a/framework/core/src/Http/RouteHandlerFactory.php +++ b/framework/core/src/Http/RouteHandlerFactory.php @@ -10,6 +10,7 @@ namespace Flarum\Http; use Closure; +use Flarum\Api\JsonApi; use Flarum\Frontend\Controller as FrontendController; use Illuminate\Contracts\Container\Container; use InvalidArgumentException; @@ -45,8 +46,8 @@ class RouteHandlerFactory public function toApiResource(string $resourceClass, string $endpointClass): Closure { return function (Request $request, array $routeParams) use ($resourceClass, $endpointClass) { - /** @var \Flarum\Api\JsonApi $api */ - $api = $this->container->make(\Flarum\Api\JsonApi::class); + /** @var JsonApi $api */ + $api = $this->container->make(JsonApi::class); $api->validateQueryParameters($request);