1
0
mirror of https://github.com/flarum/core.git synced 2025-08-04 07:27:39 +02:00

chore: adapt

This commit is contained in:
Sami Mazouz
2024-05-10 20:29:44 +01:00
parent d9e5ab4f11
commit aa39d0c11b
25 changed files with 323 additions and 221 deletions

View File

@@ -45,13 +45,13 @@ return [
->fields(PostResourceFields::class)
->endpoint(
[Endpoint\Index::class, Endpoint\Show::class, Endpoint\Create::class, Endpoint\Update::class],
function (Endpoint\Index|Endpoint\Show|Endpoint\Create|Endpoint\Update $endpoint): Endpoint\EndpointInterface {
function (Endpoint\Index|Endpoint\Show|Endpoint\Create|Endpoint\Update $endpoint): Endpoint\Endpoint {
return $endpoint->addDefaultInclude(['likes']);
}
),
(new Extend\ApiResource(Resource\DiscussionResource::class))
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint): Endpoint\EndpointInterface {
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint): Endpoint\Endpoint {
return $endpoint->addDefaultInclude(['posts.likes']);
}),

View File

@@ -63,7 +63,7 @@ return [
(new Extend\ApiResource(Resource\PostResource::class))
->fields(PostResourceFields::class)
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\EndpointInterface {
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\Endpoint {
return $endpoint->addDefaultInclude(['mentionedBy', 'mentionedBy.user', 'mentionedBy.discussion']);
})
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint): Endpoint\Index {
@@ -137,7 +137,7 @@ return [
}),
(new Extend\ApiResource(Resource\PostResource::class))
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\EndpointInterface {
->endpoint([Endpoint\Index::class, Endpoint\Show::class], function (Endpoint\Index|Endpoint\Show $endpoint): Endpoint\Endpoint {
return $endpoint->eagerLoad(['mentionsTags']);
}),
]),

View File

@@ -9,7 +9,7 @@
namespace Flarum\Api;
use Flarum\Api\Endpoint\EndpointInterface;
use Flarum\Api\Endpoint\Endpoint;
use Flarum\Foundation\AbstractServiceProvider;
use Flarum\Foundation\ErrorHandling\JsonApiFormatter;
use Flarum\Foundation\ErrorHandling\Registry;
@@ -22,7 +22,6 @@ use Flarum\Http\UrlGenerator;
use Illuminate\Contracts\Container\Container;
use Laminas\Stratigility\MiddlewarePipe;
use ReflectionClass;
use Tobyz\JsonApiServer\Endpoint\Endpoint;
class ApiServiceProvider extends AbstractServiceProvider
{
@@ -199,7 +198,7 @@ class ApiServiceProvider extends AbstractServiceProvider
* None of the injected dependencies should be directly used within
* the `endpoints` method. Encourage using callbacks.
*
* @var array<EndpointInterface> $endpoints
* @var array<Endpoint> $endpoints
*/
$endpoints = $resource->resolveEndpoints(true);

View File

@@ -57,6 +57,7 @@ class Context extends BaseContext
$fields = [];
// @phpstan-ignore-next-line
foreach ($resource->resolveFields() as $field) {
$fields[$field->name] = $field;
}
@@ -156,10 +157,13 @@ class Context extends BaseContext
return $new;
}
public function extractIdFromPath(\Tobyz\JsonApiServer\Context $context): ?string
public function extractIdFromPath(BaseContext $context): ?string
{
/** @var Endpoint\Endpoint $endpoint */
$endpoint = $context->endpoint;
$currentPath = trim($context->path(), '/');
$path = trim($context->collection->name().$this->endpoint->path, '/');
$path = trim($context->collection->name().$endpoint->path, '/');
if (! str_contains($path, '{id}')) {
return null;

View File

@@ -10,9 +10,9 @@
namespace Flarum\Api\Endpoint\Concerns;
use Closure;
use Flarum\Api\Resource\AbstractResource;
use Flarum\Http\RequestUtil;
use Tobyz\JsonApiServer\Context;
use Tobyz\JsonApiServer\Resource\AbstractResource;
use Tobyz\JsonApiServer\Schema\Sort;
trait ExtractsListingParams
@@ -110,11 +110,13 @@ trait ExtractsListingParams
public function getAvailableSorts(Context $context): array
{
if (! $context->collection instanceof AbstractResource) {
$collection = $context->collection;
if (! $collection instanceof AbstractResource) {
return [];
}
$asc = collect($context->collection->resolveSorts())
$asc = collect($collection->resolveSorts())
->filter(fn (Sort $field) => $field->isVisible($context))
->pluck('name')
->toArray();

View File

@@ -9,6 +9,7 @@
namespace Flarum\Api\Endpoint\Concerns;
use Closure;
use Flarum\Api\Resource\AbstractDatabaseResource;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
@@ -36,7 +37,7 @@ trait HasEagerLoading
*
* @param string|string[] $relations
*/
public function eagerLoad(array|string|callable $relations): static
public function eagerLoad(array|string|Closure $relations): static
{
if (! is_callable($relations)) {
$this->loadRelations = array_merge($this->loadRelations, array_map('strval', (array) $relations));

View File

@@ -9,6 +9,8 @@
namespace Flarum\Api\Endpoint\Concerns;
use Flarum\Api\Schema\Concerns\FlarumField;
use Flarum\Api\Schema\Concerns\HasValidationRules;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Translation\ArrayLoader;
use Illuminate\Translation\Translator;
@@ -30,6 +32,7 @@ trait SavesAndValidatesData
/**
* Assert that the field values within a data object pass validation.
*
* @param \Flarum\Api\Context $context
* @throws UnprocessableEntityException
*/
protected function assertDataValid(Context $context, array $data): void
@@ -49,14 +52,17 @@ trait SavesAndValidatesData
foreach ($context->fields($context->resource) as $field) {
$writable = $field->isWritable($context->withField($field));
if (! $writable) {
if (! $writable || ! in_array(HasValidationRules::class, class_uses_recursive($field))) {
continue;
}
$type = $field instanceof Attribute ? 'attributes' : 'relationships';
// @phpstan-ignore-next-line
$rules[$type] = array_merge($rules[$type], $field->getValidationRules($context));
// @phpstan-ignore-next-line
$messages = array_merge($messages, $field->getValidationMessages($context));
// @phpstan-ignore-next-line
$attributes = array_merge($attributes, $field->getValidationAttributes($context));
}

View File

@@ -15,6 +15,7 @@ use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
use Flarum\Api\Endpoint\Concerns\IncludesData;
use Flarum\Api\Endpoint\Concerns\SavesAndValidatesData;
use Flarum\Api\Endpoint\Concerns\ShowsResources;
use Flarum\Api\Resource\AbstractResource;
use Flarum\Database\Eloquent\Collection;
use RuntimeException;
use Tobyz\JsonApiServer\Resource\Creatable;
@@ -56,8 +57,11 @@ class Create extends Endpoint
$data = $this->parseData($context);
/** @var AbstractResource $resource */
$resource = $context->resource($data['type']);
$context = $context
->withResource($resource = $context->resource($data['type']))
->withResource($resource)
->withModel($model = $collection->newModel($context));
$this->assertFieldsValid($context, $data);

View File

@@ -12,6 +12,7 @@ namespace Flarum\Api\Endpoint;
use Flarum\Api\Context;
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
use Flarum\Api\Resource\AbstractResource;
use Nyholm\Psr7\Response;
use RuntimeException;
use Tobyz\JsonApiServer\Resource\Deletable;
@@ -36,9 +37,10 @@ class Delete extends Endpoint
->action(function (Context $context) {
$model = $context->model;
$context = $context->withResource(
$resource = $context->resource($context->collection->resource($model, $context)),
);
/** @var AbstractResource $resource */
$resource = $context->resource($context->collection->resource($model, $context));
$context = $context->withResource($resource);
if (! $resource instanceof Deletable) {
throw new RuntimeException(

View File

@@ -16,6 +16,7 @@ use Flarum\Api\Endpoint\Concerns\HasAuthorization;
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
use Flarum\Api\Endpoint\Concerns\HasEagerLoading;
use Flarum\Api\Endpoint\Concerns\ShowsResources;
use Flarum\Api\Resource\AbstractResource;
use Psr\Http\Message\ResponseInterface as Response;
use RuntimeException;
use Tobyz\JsonApiServer\Endpoint\Concerns\FindsResources;
@@ -24,7 +25,7 @@ use Tobyz\JsonApiServer\Exception\MethodNotAllowedException;
use function Tobyz\JsonApiServer\json_api_response;
class Endpoint implements EndpointInterface
class Endpoint implements \Tobyz\JsonApiServer\Endpoint\Endpoint
{
use ShowsResources;
use FindsResources;
@@ -116,8 +117,11 @@ class Endpoint implements EndpointInterface
throw new MethodNotAllowedException();
}
/** @var AbstractResource $collection */
$collection = $context->collection;
$context = $context->withModelId(
$context->collection->id($context)
$collection->id($context)
);
if ($context->modelId) {

View File

@@ -1,15 +0,0 @@
<?php
/*
* This file is part of Flarum.
*
* For detailed copyright and license information, please view the
* LICENSE file that was distributed with this source code.
*/
namespace Flarum\Api\Endpoint;
interface EndpointInterface extends \Tobyz\JsonApiServer\Endpoint\Endpoint
{
//
}

View File

@@ -15,6 +15,7 @@ use Flarum\Api\Endpoint\Concerns\ExtractsListingParams;
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
use Flarum\Api\Endpoint\Concerns\IncludesData;
use Flarum\Api\Resource\AbstractResource;
use Flarum\Api\Resource\Contracts\Countable;
use Flarum\Api\Resource\Contracts\Listable;
use Flarum\Api\Serializer;
@@ -100,7 +101,9 @@ class Index extends Endpoint
$this->applySorts($query, $context);
$this->applyFilters($query, $context);
$pagination?->apply($query);
if ($pagination && method_exists($pagination, 'apply')) {
$pagination->apply($query);
}
}
return $context;
@@ -131,6 +134,7 @@ class Index extends Endpoint
throw new RuntimeException('The Index endpoint query closure must return a Context instance.');
}
} else {
/** @var Context $context */
$context = $context->withQuery($query);
$this->applySorts($query, $context);
@@ -159,6 +163,7 @@ class Index extends Endpoint
return compact('models', 'meta', 'pagination', 'total');
})
->beforeSerialization(function (Context $context, array $results) {
// @phpstan-ignore-next-line
$this->loadRelations(Collection::make($results['models']), $context, $this->getInclude($context));
})
->response(function (Context $context, array $results): Response {
@@ -204,7 +209,13 @@ class Index extends Endpoint
return;
}
$sorts = $context->collection->resolveSorts();
$collection = $context->collection;
if (! $collection instanceof AbstractResource) {
throw new RuntimeException('The collection ' . $collection::class . ' must extend ' . AbstractResource::class);
}
$sorts = $collection->resolveSorts();
foreach (parse_sort_string($sortString) as [$name, $direction]) {
foreach ($sorts as $field) {
@@ -232,8 +243,16 @@ class Index extends Endpoint
]);
}
$collection = $context->collection;
if (! $collection instanceof \Tobyz\JsonApiServer\Resource\Listable) {
throw new RuntimeException(
sprintf('%s must implement %s', $collection::class, \Tobyz\JsonApiServer\Resource\Listable::class),
);
}
try {
apply_filters($query, $filters, $context->collection, $context);
apply_filters($query, $filters, $collection, $context);
} catch (Sourceable $e) {
throw $e->prependSource(['parameter' => 'filter']);
}

View File

@@ -15,6 +15,7 @@ use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
use Flarum\Api\Endpoint\Concerns\IncludesData;
use Flarum\Api\Endpoint\Concerns\SavesAndValidatesData;
use Flarum\Api\Endpoint\Concerns\ShowsResources;
use Flarum\Api\Resource\AbstractResource;
use Flarum\Database\Eloquent\Collection;
use RuntimeException;
use Tobyz\JsonApiServer\Resource\Updatable;
@@ -38,9 +39,10 @@ class Update extends Endpoint
->action(function (Context $context): object {
$model = $context->model;
$context = $context->withResource(
$resource = $context->resource($context->collection->resource($model, $context)),
);
/** @var AbstractResource $resource */
$resource = $context->resource($context->collection->resource($model, $context));
$context = $context->withResource($resource);
if (! $resource instanceof Updatable) {
throw new RuntimeException(

View File

@@ -9,14 +9,16 @@
namespace Flarum\Api;
use Flarum\Api\Endpoint\EndpointInterface;
use Flarum\Api\Endpoint\Endpoint;
use Flarum\Api\Resource\AbstractDatabaseResource;
use Flarum\Api\Resource\AbstractResource;
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 as Request;
use RuntimeException;
use Tobyz\JsonApiServer\Exception\BadRequestException;
use Tobyz\JsonApiServer\Exception\ResourceNotFoundException;
use Tobyz\JsonApiServer\JsonApi as BaseJsonApi;
@@ -57,9 +59,13 @@ class JsonApi extends BaseJsonApi
->withEndpoint($this->findEndpoint($collection));
}
protected function findEndpoint(?Collection $collection): EndpointInterface
protected function findEndpoint(?Collection $collection): Endpoint
{
/** @var EndpointInterface $endpoint */
if (! $collection instanceof AbstractResource) {
throw new RuntimeException('Resource ' . $collection::class . ' must extend ' . AbstractResource::class);
}
/** @var Endpoint $endpoint */
foreach ($collection->resolveEndpoints() as $endpoint) {
if ($endpoint->name === $this->endpointName) {
return $endpoint;
@@ -152,13 +158,19 @@ class JsonApi extends BaseJsonApi
$context = $context->withInternal($key, $value);
}
$endpoint = $context->endpoint;
if (! $endpoint instanceof Endpoint) {
throw new RuntimeException('The endpoint ' . $endpoint::class . ' must extend ' . Endpoint::class);
}
$context = $context->withRequest(
$request
->withMethod($context->endpoint->method)
->withUri(new Uri($context->endpoint->path))
->withMethod($endpoint->method)
->withUri(new Uri($endpoint->path))
);
return $context->endpoint->process($context);
return $endpoint->process($context);
}
public function validateQueryParameters(Request $request): void

View File

@@ -17,6 +17,7 @@ use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Support\Str;
use InvalidArgumentException;
@@ -31,7 +32,7 @@ use Tobyz\JsonApiServer\Schema\Type\DateTime;
/**
* @template M of Model
* @extends AbstractResource<M, FlarumContext>
* @extends AbstractResource<M>
*/
abstract class AbstractDatabaseResource extends AbstractResource implements
Contracts\Findable,
@@ -42,10 +43,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
Contracts\Updatable,
Contracts\Deletable
{
use DispatchEventsTrait {
dispatchEventsFor as traitDispatchEventsFor;
}
abstract public function model(): string;
/**
@@ -89,7 +86,7 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
* @param M $model
* @param FlarumContext $context
*/
protected function getAttributeValue(Model $model, Field $field, Context $context)
protected function getAttributeValue(Model $model, Field $field, Context $context): mixed
{
if ($field instanceof RelationAggregator && ($aggregate = $field->getRelationAggregate())) {
$relationName = $aggregate['relation'];
@@ -98,6 +95,7 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
return $model->getAttribute($this->property($field));
}
/** @var Relationship|null $relationship */
$relationship = collect($context->fields($this))->first(fn ($f) => $f->name === $relationName);
if (! $relationship) {
@@ -120,7 +118,7 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
* @param M $model
* @param FlarumContext $context
*/
protected function getRelationshipValue(Model $model, Relationship $field, Context $context)
protected function getRelationshipValue(Model $model, Relationship $field, Context $context): mixed
{
$method = $this->method($field);
@@ -135,7 +133,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
if ($key = $model->getAttribute($relation->getForeignKeyName())) {
if ($relation instanceof MorphTo) {
$morphType = $model->{$relation->getMorphType()};
$morphType = MorphTo::getMorphedModel($morphType) ?? $morphType;
$related = $relation->createModelByType($morphType);
} else {
$related = $relation->getRelated();
@@ -222,10 +219,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
*/
public function find(string $id, Context $context): ?object
{
if ($id === null) {
return null;
}
return $this->query($context)->find($id);
}
@@ -282,27 +275,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
}
}
/**
* @param M $model
* @param FlarumContext $context
*/
public function createAction(object $model, Context $context): object
{
$model = $this->creating($model, $context) ?: $model;
$model = $this->saving($model, $context) ?: $model;
$model = $this->create($model, $context);
$model = $this->saved($model, $context) ?: $model;
$model = $this->created($model, $context) ?: $model;
$this->dispatchEventsFor($model, $context->getActor());
return $model;
}
/**
* @param M $model
* @param FlarumContext $context
@@ -314,27 +286,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
return $model;
}
/**
* @param M $model
* @param FlarumContext $context
*/
public function updateAction(object $model, Context $context): object
{
$model = $this->updating($model, $context) ?: $model;
$model = $this->saving($model, $context) ?: $model;
$this->update($model, $context);
$model = $this->saved($model, $context) ?: $model;
$model = $this->updated($model, $context) ?: $model;
$this->dispatchEventsFor($model, $context->getActor());
return $model;
}
/**
* @param M $model
* @param FlarumContext $context
@@ -355,21 +306,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
$model->save();
}
/**
* @param M $model
* @param FlarumContext $context
*/
public function deleteAction(object $model, Context $context): void
{
$this->deleting($model, $context);
$this->delete($model, $context);
$this->deleted($model, $context);
$this->dispatchEventsFor($model, $context->getActor());
}
/**
* @param M $model
* @param FlarumContext $context
@@ -406,91 +342,6 @@ abstract class AbstractDatabaseResource extends AbstractResource implements
throw new RuntimeException('Not supported in Flarum, please use a model searcher instead https://docs.flarum.org/extend/search.');
}
/**
* @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
{
//
}
public function dispatchEventsFor(mixed $entity, User $actor = null): void
{
if (method_exists($entity, 'releaseEvents')) {
$this->traitDispatchEventsFor($entity, $actor);
}
}
/**
* @param FlarumContext $context
*/

View File

@@ -12,18 +12,19 @@ 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\HasHooks;
use Flarum\Api\Resource\Concerns\HasSortMap;
use Tobyz\JsonApiServer\Resource\AbstractResource as BaseResource;
/**
* @template M of object
* @extends BaseResource<M, Context>
*/
abstract class AbstractResource extends BaseResource
{
use Bootable;
use Extendable;
use HasSortMap;
use HasHooks;
public function id(Context $context): ?string
{

View File

@@ -0,0 +1,182 @@
<?php
namespace Flarum\Api\Resource\Concerns;
use Flarum\Api\Context as FlarumContext;
use Flarum\Api\Resource\Contracts\Creatable;
use Flarum\Api\Resource\Contracts\Deletable;
use Flarum\Api\Resource\Contracts\Updatable;
use Flarum\Foundation\DispatchEventsTrait;
use Flarum\User\User;
use RuntimeException;
use Tobyz\JsonApiServer\Context;
/**
* @template M of object
*/
trait HasHooks
{
use DispatchEventsTrait {
dispatchEventsFor as traitDispatchEventsFor;
}
/**
* @param M $model
* @param FlarumContext $context
*/
public function createAction(object $model, Context $context): object
{
if (! $this instanceof Creatable) {
throw new RuntimeException(
sprintf('%s must implement %s', get_class($this), Creatable::class),
);
}
$model = $this->creating($model, $context) ?: $model;
$model = $this->saving($model, $context) ?: $model;
$model = $this->create($model, $context);
$model = $this->saved($model, $context) ?: $model;
$model = $this->created($model, $context) ?: $model;
$this->dispatchEventsFor($model, $context->getActor());
return $model;
}
/**
* @param M $model
* @param FlarumContext $context
*/
public function updateAction(object $model, Context $context): object
{
if (! $this instanceof Updatable) {
throw new RuntimeException(
sprintf('%s must implement %s', get_class($this), Updatable::class),
);
}
$model = $this->updating($model, $context) ?: $model;
$model = $this->saving($model, $context) ?: $model;
$this->update($model, $context);
$model = $this->saved($model, $context) ?: $model;
$model = $this->updated($model, $context) ?: $model;
$this->dispatchEventsFor($model, $context->getActor());
return $model;
}
/**
* @param M $model
* @param FlarumContext $context
*/
public function deleteAction(object $model, Context $context): void
{
if (! $this instanceof Deletable) {
throw new RuntimeException(
sprintf('%s must implement %s', get_class($this), Deletable::class),
);
}
$this->deleting($model, $context);
$this->delete($model, $context);
$this->deleted($model, $context);
$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
{
//
}
public function dispatchEventsFor(mixed $entity, User $actor = null): void
{
if (method_exists($entity, 'releaseEvents')) {
$this->traitDispatchEventsFor($entity, $actor);
}
}
}

View File

@@ -231,10 +231,13 @@ class DiscussionResource extends AbstractDatabaseResource
$offset = $endpoint->extractOffsetValue($context, $endpoint->defaultExtracts($context));
}
/** @var Endpoint\Endpoint $endpoint */
$endpoint = $context->endpoint;
$posts = $discussion->posts()
->whereVisibleTo($actor)
->with($context->endpoint->getEagerLoadsFor('posts', $context))
->with($context->endpoint->getWhereEagerLoadsFor('posts', $context))
->with($endpoint->getEagerLoadsFor('posts', $context))
->with($endpoint->getWhereEagerLoadsFor('posts', $context))
->orderBy('number')
->skip($offset)
->take($limit)

View File

@@ -9,12 +9,13 @@
namespace Flarum\Api\Resource;
use Flarum\Api\Context;
use Flarum\Api\Endpoint\Endpoint;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
use Illuminate\Database\Eloquent\Relations\Relation;
use Tobyz\JsonApiServer\Context;
use Tobyz\JsonApiServer\Laravel\Field\ToMany;
use Tobyz\JsonApiServer\Laravel\Field\ToOne;
use Tobyz\JsonApiServer\Schema\Field\Relationship;
@@ -25,21 +26,21 @@ abstract class EloquentBuffer
public static function add(Model $model, string $relationName, ?array $aggregate = null): void
{
static::$buffer[get_class($model)][$relationName][$aggregate ? $aggregate['column'].$aggregate['function'] : 'normal'][] = $model;
self::$buffer[get_class($model)][$relationName][$aggregate ? $aggregate['column'].$aggregate['function'] : 'normal'][] = $model;
}
public static function getBuffer(Model $model, string $relationName, ?array $aggregate = null): ?array
{
return static::$buffer[get_class($model)][$relationName][$aggregate ? $aggregate['column'].$aggregate['function'] : 'normal'] ?? null;
return self::$buffer[get_class($model)][$relationName][$aggregate ? $aggregate['column'].$aggregate['function'] : 'normal'] ?? null;
}
public static function setBuffer(Model $model, string $relationName, ?array $aggregate, array $buffer): void
{
static::$buffer[get_class($model)][$relationName][$aggregate ? $aggregate['column'].$aggregate['function'] : 'normal'] = $buffer;
self::$buffer[get_class($model)][$relationName][$aggregate ? $aggregate['column'].$aggregate['function'] : 'normal'] = $buffer;
}
/**
* @param array{relation: string, column: string, function: string, constrain: Closure}|null $aggregate
* @param array{relation: string, column: string, function: string, constrain: callable|null}|null $aggregate
*/
public static function load(
Model $model,
@@ -48,7 +49,7 @@ abstract class EloquentBuffer
Context $context,
?array $aggregate = null,
): void {
if (! ($models = static::getBuffer($model, $relationName, $aggregate))) {
if (! ($models = self::getBuffer($model, $relationName, $aggregate))) {
return;
}
@@ -64,6 +65,7 @@ abstract class EloquentBuffer
// may be multiple if this is a polymorphic relationship. We
// start by getting the resource types this relationship
// could possibly contain.
/** @var AbstractDatabaseResource[] $resources */
$resources = $context->api->resources;
if ($type = $relationship->collections) {
@@ -81,9 +83,12 @@ abstract class EloquentBuffer
if ($resource instanceof AbstractDatabaseResource && ! isset($constrain[$modelClass])) {
$constrain[$modelClass] = function (Builder $query) use ($resource, $context, $relationship, $aggregate) {
if (! $aggregate) {
/** @var Endpoint $endpoint */
$endpoint = $context->endpoint;
$query
->with($context->endpoint->getEagerLoadsFor($relationship->name, $context))
->with($context->endpoint->getWhereEagerLoadsFor($relationship->name, $context));
->with($endpoint->getEagerLoadsFor($relationship->name, $context))
->with($endpoint->getWhereEagerLoadsFor($relationship->name, $context));
}
$resource->scope($query, $context);
@@ -115,8 +120,10 @@ abstract class EloquentBuffer
// Set the inverse relation on the loaded relations.
$collection->each(function (Model $model) use ($relationName, $relationship) {
/** @var Model|Collection $related */
if ($related = $model->getRelation($relationName)) {
/** @var Model|Collection|null $related */
$related = $model->getRelation($relationName);
if ($related) {
$inverse = $relationship->inverse ?? str($model::class)->afterLast('\\')->camel()->toString();
$related = $related instanceof Collection ? $related : [$related];
@@ -132,6 +139,6 @@ abstract class EloquentBuffer
$collection->loadAggregate([$relationName => $loader], $aggregate['column'], $aggregate['function']);
}
static::setBuffer($model, $relationName, $aggregate, []);
self::setBuffer($model, $relationName, $aggregate, []);
}
}

View File

@@ -58,6 +58,7 @@ class PostResource extends AbstractDatabaseResource
$query->whereVisibleTo($context->getActor());
}
/** @inheritDoc */
public function newModel(\Tobyz\JsonApiServer\Context $context): object
{
if ($context->creating(self::class)) {

View File

@@ -9,12 +9,14 @@
namespace Flarum\Api\Schema\Contracts;
use Closure;
interface RelationAggregator
{
public function relationAggregate(string $relation, string $column, string $function): static;
/**
* @return array{relation: string, column: string, function: string}|null
* @return array{relation: string, column: string, function: string, constrain: Closure|null}|null
*/
public function getRelationAggregate(): ?array;
}

View File

@@ -9,13 +9,12 @@
namespace Flarum\Extend;
use Flarum\Api\Endpoint\EndpointInterface;
use Flarum\Api\Endpoint\Endpoint;
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;
@@ -179,7 +178,7 @@ class ApiResource implements ExtenderInterface
$resourceClass::mutateEndpoints(
/**
* @var EndpointInterface[] $endpoints
* @var Endpoint[] $endpoints
*/
function (array $endpoints, Resource $resource) use ($container): array {
foreach ($this->endpoints as $newEndpointsCallback) {
@@ -203,8 +202,8 @@ class ApiResource implements ExtenderInterface
$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 (! $endpoint instanceof Endpoint) {
throw new RuntimeException('The endpoint mutator must return an instance of '.Endpoint::class);
}
}
}

View File

@@ -18,6 +18,7 @@ parameters:
# We know for a fact the JsonApi object used internally is always the Flarum one.
- stubs/Tobyz/JsonApiServer/JsonApi.stub
- stubs/Tobyz/JsonApiServer/Context.stub
services:
-

View File

@@ -40,3 +40,7 @@ parameters:
# 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\.$#'
# Ignore overriden classes from packages so that it's always easier to keep track of what's being overriden.
- message: '#^Method Flarum\\Api\\Serializer\:\:[A-z0-9_]+\(\) has parameter \$[A-z0-9_]+ with no type specified\.$#'
- message: '#^Method Flarum\\Api\\Endpoint\\[A-z0-9_]+\:\:[A-z0-9_]+\(\) has parameter \$[A-z0-9_]+ with no type specified\.$#'

View File

@@ -0,0 +1,11 @@
<?php
namespace Tobyz\JsonApiServer;
/**
* @mixin \Flarum\Api\Context
*/
class Context
{
}