mirror of
https://github.com/flarum/core.git
synced 2025-08-03 23:17:43 +02:00
chore: adapt
This commit is contained in:
@@ -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']);
|
||||
}),
|
||||
|
||||
|
@@ -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']);
|
||||
}),
|
||||
]),
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
|
@@ -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));
|
||||
|
@@ -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));
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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(
|
||||
|
@@ -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) {
|
||||
|
@@ -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
|
||||
{
|
||||
//
|
||||
}
|
@@ -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']);
|
||||
}
|
||||
|
@@ -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(
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
*/
|
||||
|
@@ -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
|
||||
{
|
||||
|
182
framework/core/src/Api/Resource/Concerns/HasHooks.php
Normal file
182
framework/core/src/Api/Resource/Concerns/HasHooks.php
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -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, []);
|
||||
}
|
||||
}
|
||||
|
@@ -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)) {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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:
|
||||
-
|
||||
|
@@ -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\.$#'
|
||||
|
11
php-packages/phpstan/stubs/Tobyz/JsonApiServer/Context.stub
Normal file
11
php-packages/phpstan/stubs/Tobyz/JsonApiServer/Context.stub
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Tobyz\JsonApiServer;
|
||||
|
||||
/**
|
||||
* @mixin \Flarum\Api\Context
|
||||
*/
|
||||
class Context
|
||||
{
|
||||
|
||||
}
|
Reference in New Issue
Block a user