Skip to content

Commit

Permalink
temp
Browse files Browse the repository at this point in the history
  • Loading branch information
soyuka committed Dec 17, 2024
1 parent aaff5c5 commit e36f2a8
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 68 deletions.
57 changes: 57 additions & 0 deletions src/JsonApi/Filter/SparseFieldset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\JsonApi\Filter;

use ApiPlatform\Metadata\JsonSchemaFilterInterface;
use ApiPlatform\Metadata\OpenApiParameterFilterInterface;
use ApiPlatform\Metadata\Parameter as MetadataParameter;
use ApiPlatform\Metadata\ParameterProviderFilterInterface;
use ApiPlatform\Metadata\PropertiesFilterInterface;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\OpenApi\Model\Parameter;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;

final class SparseFieldset implements OpenApiParameterFilterInterface, JsonSchemaFilterInterface, ParameterProviderFilterInterface, PropertiesFilterInterface
{
public function getSchema(MetadataParameter $parameter): array
{
return [
'type' => 'array',
'items' => [
'type' => 'string',
],
];
}

public function getOpenApiParameters(MetadataParameter $parameter): Parameter|array|null
{
$example = \sprintf(
'%1$s[]={propertyName}&%1$s[]={anotherPropertyName}',
$parameter->getKey()
);

return new Parameter(
name: $parameter->getKey().'[]',
in: $parameter instanceof QueryParameter ? 'query' : 'header',
description: 'Allows you to reduce the response to contain only the properties you need. If your desired property is nested, you can address it using nested arrays. Example: '.$example
);
}

public static function getParameterProvider(): string
{
return SparseFieldsetParameterProvider::class;
}
}
61 changes: 61 additions & 0 deletions src/JsonApi/Filter/SparseFieldsetParameterProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

/*
* This file is part of the API Platform project.
*
* (c) Kévin Dunglas <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace ApiPlatform\JsonApi\Filter;

use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use ApiPlatform\State\ParameterProviderInterface;
use Symfony\Component\Serializer\Normalizer\AbstractNormalizer;

final readonly class SparseFieldsetParameterProvider implements ParameterProviderInterface
{
public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation
{
if (!($operation = $context['operation'] ?? null)) {
return null;
}

$allowedProperties = $parameter->getExtraProperties()['_properties'] ?? [];
$value = $parameter->getValue();
$normalizationContext = $operation->getNormalizationContext();

if (!is_array($value)) {
return null;
}

$properties = [];
$shortName = strtolower($operation->getShortName());
foreach ($value as $resource => $fields) {
if (strtolower($resource) === $shortName) {
$p = &$properties;
} else {
$properties[$resource] = [];
$p = &$properties[$resource];
}

foreach (explode(',', $fields) as $f) {
if (array_key_exists($f, $allowedProperties)) {
$p[] = $f;
}
}
}

if (isset($normalizationContext[AbstractNormalizer::ATTRIBUTES])) {
$properties = array_merge_recursive((array) $normalizationContext[AbstractNormalizer::ATTRIBUTES], $properties);
}

$normalizationContext[AbstractNormalizer::ATTRIBUTES] = $properties;
return $operation;
}
}
48 changes: 33 additions & 15 deletions src/Laravel/ApiPlatformProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
use ApiPlatform\Hydra\Serializer\HydraPrefixNameConverter;
use ApiPlatform\Hydra\Serializer\PartialCollectionViewNormalizer as HydraPartialCollectionViewNormalizer;
use ApiPlatform\Hydra\State\HydraLinkProcessor;
use ApiPlatform\JsonApi\Filter\SparseFieldset;
use ApiPlatform\JsonApi\Filter\SparseFieldsetParameterProvider;
use ApiPlatform\JsonApi\JsonSchema\SchemaFactory as JsonApiSchemaFactory;
use ApiPlatform\JsonApi\Serializer\CollectionNormalizer as JsonApiCollectionNormalizer;
use ApiPlatform\JsonApi\Serializer\EntrypointNormalizer as JsonApiEntrypointNormalizer;
Expand Down Expand Up @@ -108,6 +110,7 @@
use ApiPlatform\Laravel\Exception\ErrorHandler;
use ApiPlatform\Laravel\GraphQl\Controller\EntrypointController as GraphQlEntrypointController;
use ApiPlatform\Laravel\GraphQl\Controller\GraphiQlController;
use ApiPlatform\Laravel\JsonApi\State\JsonApiProvider;
use ApiPlatform\Laravel\Metadata\CachePropertyMetadataFactory;
use ApiPlatform\Laravel\Metadata\CachePropertyNameCollectionMetadataFactory;
use ApiPlatform\Laravel\Metadata\CacheResourceCollectionMetadataFactory;
Expand Down Expand Up @@ -234,7 +237,7 @@ class ApiPlatformProvider extends ServiceProvider
*/
public function register(): void
{
$this->mergeConfigFrom(__DIR__.'/config/api-platform.php', 'api-platform');
$this->mergeConfigFrom(__DIR__ . '/config/api-platform.php', 'api-platform');

$this->app->singleton(PropertyInfoExtractorInterface::class, function () {
$phpDocExtractor = class_exists(DocBlockFactory::class) ? new PhpDocExtractor() : null;
Expand Down Expand Up @@ -423,7 +426,15 @@ public function register(): void

$this->app->bind(OperationMetadataFactoryInterface::class, OperationMetadataFactory::class);

$this->app->tag([EqualsFilter::class, PartialSearchFilter::class, DateFilter::class, OrderFilter::class, RangeFilter::class, SortFilter::class], EloquentFilterInterface::class);
$this->app->tag([
EqualsFilter::class,
PartialSearchFilter::class,
DateFilter::class,
OrderFilter::class,
RangeFilter::class,
SortFilter::class,
SparseFieldset::class,
], EloquentFilterInterface::class);

$this->app->bind(FilterQueryExtension::class, function (Application $app) {
$tagged = iterator_to_array($app->tagged(EloquentFilterInterface::class));
Expand Down Expand Up @@ -470,6 +481,13 @@ public function register(): void
return new DeserializeProvider($app->make(ValidateProvider::class), $app->make(SerializerInterface::class), $app->make(SerializerContextBuilderInterface::class));
});

if (class_exists(JsonApiProvider::class)) {
$this->app->extend(DeserializeProvider::class, function (ProviderInterface $inner, Application $app) {
return new JsonApiProvider($inner);
});
}


$this->app->tag([PropertyFilter::class], SerializerFilterInterface::class);

$this->app->singleton(SerializerFilterParameterProvider::class, function (Application $app) {
Expand All @@ -482,7 +500,7 @@ public function register(): void
$this->app->singleton(SortFilterParameterProvider::class, function (Application $app) {
return new SortFilterParameterProvider();
});
$this->app->tag([SerializerFilterParameterProvider::class, SortFilterParameterProvider::class], ParameterProviderInterface::class);
$this->app->tag([SerializerFilterParameterProvider::class, SortFilterParameterProvider::class, SparseFieldsetParameterProvider::class], ParameterProviderInterface::class);

$this->app->singleton('filters', function (Application $app) {
return new ServiceLocator(array_merge(
Expand Down Expand Up @@ -634,7 +652,7 @@ public function register(): void
$parts = explode('?', $request->server->get('REQUEST_URI'), 2);
$trimmedRequest->server->set(
'REQUEST_URI',
rtrim($parts[0], '/').(isset($parts[1]) ? '?'.$parts[1] : '')
rtrim($parts[0], '/') . (isset($parts[1]) ? '?' . $parts[1] : '')
);

$urlGenerator = new UrlGeneratorRouter($app->make(Router::class));
Expand Down Expand Up @@ -1331,15 +1349,15 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
{
if ($this->app->runningInConsole()) {
$this->publishes([
__DIR__.'/config/api-platform.php' => $this->app->configPath('api-platform.php'),
__DIR__ . '/config/api-platform.php' => $this->app->configPath('api-platform.php'),
], 'api-platform-config');

$this->publishes([
__DIR__.'/public' => $this->app->publicPath('vendor/api-platform'),
__DIR__ . '/public' => $this->app->publicPath('vendor/api-platform'),
], ['api-platform-assets', 'public']);
}

$this->loadViewsFrom(__DIR__.'/resources/views', 'api-platform');
$this->loadViewsFrom(__DIR__ . '/resources/views', 'api-platform');

$config = $this->app['config'];

Expand All @@ -1360,13 +1378,13 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
foreach ($resourceMetadata->getOperations() as $operation) {
$uriTemplate = $operation->getUriTemplate();
// _format is read by the middleware
$uriTemplate = $operation->getRoutePrefix().str_replace('{._format}', '{_format?}', $uriTemplate);
$uriTemplate = $operation->getRoutePrefix() . str_replace('{._format}', '{_format?}', $uriTemplate);
$route = (new Route([$operation->getMethod()], $uriTemplate, [ApiPlatformController::class, '__invoke']))
->where('_format', '^\.[a-zA-Z]+')
->name($operation->getName())
->setDefaults(['_api_operation_name' => $operation->getName(), '_api_resource_class' => $operation->getClass()]);

$route->middleware(ApiPlatformMiddleware::class.':'.$operation->getName());
$route->middleware(ApiPlatformMiddleware::class . ':' . $operation->getName());
$route->middleware($globalMiddlewares);
$route->middleware($operation->getMiddleware());

Expand All @@ -1376,12 +1394,12 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
}

$prefix = $config->get('api-platform.defaults.route_prefix') ?? '';
$route = new Route(['GET'], $prefix.'/contexts/{shortName?}{_format?}', [ContextAction::class, '__invoke']);
$route = new Route(['GET'], $prefix . '/contexts/{shortName?}{_format?}', [ContextAction::class, '__invoke']);
$route->name('api_jsonld_context');
$route->middleware(ApiPlatformMiddleware::class);
$route->middleware($globalMiddlewares);
$routeCollection->add($route);
$route = new Route(['GET'], $prefix.'/docs{_format?}', function (Request $request, Application $app) {
$route = new Route(['GET'], $prefix . '/docs{_format?}', function (Request $request, Application $app) {
$documentationAction = $app->make(DocumentationController::class);

return $documentationAction->__invoke($request);
Expand All @@ -1391,7 +1409,7 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
$route->middleware($globalMiddlewares);
$routeCollection->add($route);

$route = new Route(['GET'], $prefix.'/.well-known/genid/{id}', function (): void {
$route = new Route(['GET'], $prefix . '/.well-known/genid/{id}', function (): void {
throw new NotExposedHttpException('This route is not exposed on purpose. It generates an IRI for a collection resource without identifier nor item operation.');
});
$route->name('api_genid');
Expand All @@ -1400,15 +1418,15 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
$routeCollection->add($route);

if ($config->get('api-platform.graphql.enabled')) {
$route = new Route(['POST', 'GET'], $prefix.'/graphql', function (Application $app, Request $request) {
$route = new Route(['POST', 'GET'], $prefix . '/graphql', function (Application $app, Request $request) {
$entrypointAction = $app->make(GraphQlEntrypointController::class);

return $entrypointAction->__invoke($request);
});
$route->middleware($globalMiddlewares);
$routeCollection->add($route);

$route = new Route(['GET'], $prefix.'/graphiql', function (Application $app) {
$route = new Route(['GET'], $prefix . '/graphiql', function (Application $app) {
$controller = $app->make(GraphiQlController::class);

return $controller->__invoke();
Expand All @@ -1417,7 +1435,7 @@ public function boot(ResourceNameCollectionFactoryInterface $resourceNameCollect
$routeCollection->add($route);
}

$route = new Route(['GET'], $prefix.'/{index?}{_format?}', function (Request $request, Application $app) {
$route = new Route(['GET'], $prefix . '/{index?}{_format?}', function (Request $request, Application $app) {
$entrypointAction = $app->make(EntrypointController::class);

return $entrypointAction->__invoke($request);
Expand Down
31 changes: 14 additions & 17 deletions src/Laravel/Eloquent/Filter/JsonApi/SortFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
namespace ApiPlatform\Laravel\Eloquent\Filter\JsonApi;

use ApiPlatform\Laravel\Eloquent\Filter\FilterInterface;
use ApiPlatform\Laravel\Eloquent\Filter\JsonApi\SortFilterParameterProvider;
use ApiPlatform\Laravel\Eloquent\Filter\QueryPropertyTrait;
use ApiPlatform\Metadata\JsonSchemaFilterInterface;
use ApiPlatform\Metadata\Parameter;
use ApiPlatform\Metadata\ParameterProviderFilterInterface;
Expand All @@ -25,29 +23,28 @@

final class SortFilter implements FilterInterface, JsonSchemaFilterInterface, ParameterProviderFilterInterface, PropertiesFilterInterface
{
use QueryPropertyTrait;
public const ASC = 'asc';
public const DESC = 'desc';

/**
* @param Builder<Model> $builder
* @param array<string, mixed> $context
*/
public function apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder
{
dd('hi');
// if (!\is_string($values)) {
// $properties = $parameter->getExtraProperties()['_properties'] ?? [];
//
// foreach ($values as $key => $value) {
// if (!isset($properties[$key])) {
// continue;
// }
// $builder = $builder->orderBy($properties[$key], $value);
// }
//
// return $builder;
// }
if (!\is_array($values)) {
return $builder;
}

return $builder->orderBy($this->getQueryProperty($parameter), $values);
foreach ($values as $order => $dir) {
if (self::ASC !== $dir && self::DESC !== $dir) {
continue;
}

$builder->orderBy($order, $dir);
}

return $builder;
}

/**
Expand Down
49 changes: 24 additions & 25 deletions src/Laravel/Eloquent/Filter/JsonApi/SortFilterParameterProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,39 @@

namespace ApiPlatform\Laravel\Eloquent\Filter\JsonApi;

use ApiPlatform\Metadata\IriConverterInterface;
use ApiPlatform\Metadata\Operation;
use ApiPlatform\Metadata\Parameter;
use ApiPlatform\State\ParameterProviderInterface;
use Psr\Log\LoggerInterface;

final readonly class SortFilterParameterProvider implements ParameterProviderInterface
{
public function provide(Parameter $parameter, array $parameters = [], array $context = []): ?Operation
{
dd('hi2');
$operation = $context['operation'] ?? null;
if (!($operation = $context['operation'] ?? null)) {
return $operation;
}

$properties = $parameter->getExtraProperties()['_properties'] ?? [];
$value = $parameter->getValue();
$filter = $parameter->getFilter();
//
// if(!$value || !$filter instanceof IriSearchFilter){
// $this->logger->debug('No value or incompatible filter found.');
//
// return $operation;
// }
//
// try {
// $resource = $this->iriConverter->getResourceFromIri($value);
//
// $operation = $operation->withExtraProperties(array_merge(
// $operation->getExtraProperties() ?? [],
// ['resource' => $resource]
// ));
//
// } catch (\Exception $e) {
// $this->logger->error(sprintf('Invalid IRI "%s": %s', $value, $e->getMessage()));
// return null;
// }
//
if (!is_string($value)) {
return $operation;
}

$values = explode(',', $value);
$orderBy = [];
foreach ($values as $v) {
$dir = SortFilter::ASC;
if (str_starts_with($v, '-')) {
$dir = SortFilter::DESC;
$v = substr($v, 1);
}

if (array_key_exists($v, $properties)) {
$orderBy[$properties[$v]] = $dir;
}
}

$parameter->setValue($orderBy);
return $operation;
}
}
Loading

0 comments on commit e36f2a8

Please sign in to comment.