json-api-server/src/ResourceType.php

186 lines
5.5 KiB
PHP

<?php
/*
* This file is part of tobyz/json-api-server.
*
* (c) Toby Zerner <toby.zerner@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Tobyz\JsonApiServer;
use Tobyz\JsonApiServer\Adapter\AdapterInterface;
use Tobyz\JsonApiServer\Exception\BadRequestException;
use Tobyz\JsonApiServer\Schema\Attribute;
use Tobyz\JsonApiServer\Schema\Relationship;
use Tobyz\JsonApiServer\Schema\Type;
final class ResourceType
{
private $type;
private $adapter;
private $buildSchema;
private $schema;
public function __construct(string $type, AdapterInterface $adapter, callable $buildSchema = null)
{
$this->type = $type;
$this->adapter = $adapter;
$this->buildSchema = $buildSchema;
}
public function getType(): string
{
return $this->type;
}
public function getAdapter(): AdapterInterface
{
return $this->adapter;
}
public function getSchema(): Type
{
if (! $this->schema) {
$this->schema = new Type();
if ($this->buildSchema) {
($this->buildSchema)($this->schema);
}
}
return $this->schema;
}
/**
* Get the URL for a model.
*/
public function url($model, Context $context): string
{
$id = $this->adapter->getId($model);
return $context->getApi()->getBasePath()."/$this->type/$id";
}
/**
* Apply the resource type's scopes to a query.
*/
public function applyScopes($query, Context $context): void
{
run_callbacks(
$this->getSchema()->getListeners('scope'),
[$query, $context]
);
}
/**
* Apply the resource type's filters to a query.
*/
public function applySort($query, string $sortString, Context $context): void
{
$schema = $this->getSchema();
$customSorts = $schema->getSorts();
$fields = $schema->getFields();
foreach (parse_sort_string($sortString) as [$name, $direction]) {
if (
isset($customSorts[$name])
&& evaluate($customSorts[$name]->getVisible(), [$context])
) {
$customSorts[$name]->getCallback()($query, $direction, $context);
continue;
}
$field = $fields[$name] ?? null;
if (
$field instanceof Attribute
&& evaluate($field->getSortable(), [$context])
) {
$this->adapter->sortByAttribute($query, $field, $direction);
continue;
}
throw (new BadRequestException("Invalid sort field: $name"))->setSourceParameter('sort');
}
}
/**
* Apply the resource type's filters to a query.
*/
public function applyFilters($query, array $filters, Context $context): void
{
$schema = $this->getSchema();
$customFilters = $schema->getFilters();
$fields = $schema->getFields();
foreach ($filters as $name => $value) {
if ($name === 'id') {
$this->adapter->filterByIds($query, explode(',', $value));
continue;
}
if (
isset($customFilters[$name])
&& evaluate($customFilters[$name]->getVisible(), [$context])
) {
$customFilters[$name]->getCallback()($query, $value, $context);
continue;
}
[$name, $sub] = explode('.', $name, 2) + [null, null];
$field = $fields[$name] ?? null;
if ($field && evaluate($field->getFilterable(), [$context])) {
if ($field instanceof Attribute && $sub === null) {
$this->filterByAttribute($query, $field, $value);
continue;
}
if ($field instanceof Relationship) {
if (is_string($relatedType = $field->getType())) {
$relatedResource = $context->getApi()->getResourceType($relatedType);
$this->adapter->filterByRelationship($query, $field, function ($query) use ($relatedResource, $sub, $value, $context) {
$relatedResource->applyFilters($query, [($sub ?? 'id') => $value], $context);
});
continue;
}
throw (new BadRequestException('Cannot filter on attribute of polymorphic relationship: '.$name))
->setSourceParameter("filter[$name]");
}
}
throw (new BadRequestException("Invalid filter: $name"))->setSourceParameter("filter[$name]");
}
}
private function filterByAttribute($query, Attribute $attribute, $value): void
{
if (preg_match('/(.+)\.\.(.+)/', $value, $matches)) {
if ($matches[1] !== '*') {
$this->adapter->filterByAttribute($query, $attribute, $value, '>=');
}
if ($matches[2] !== '*') {
$this->adapter->filterByAttribute($query, $attribute, $value, '<=');
}
return;
}
foreach (['>=', '>', '<=', '<'] as $operator) {
if (strpos($value, $operator) === 0) {
$this->adapter->filterByAttribute($query, $attribute, substr($value, strlen($operator)), $operator);
return;
}
}
$this->adapter->filterByAttribute($query, $attribute, $value);
}
}