Skip to content

Commit

Permalink
Go over most documentation and refactor minor stuff
Browse files Browse the repository at this point in the history
The query builder and the Apitizer\Types still need to be done.
  • Loading branch information
drtheuns committed Jan 16, 2020
1 parent fa6cf6f commit 8bebf2b
Show file tree
Hide file tree
Showing 14 changed files with 128 additions and 54 deletions.
58 changes: 53 additions & 5 deletions src/Apitizer/Apitizer.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,89 @@

class Apitizer
{
public static function getQueryBuilders()
/**
* Get a list of fully-qualified class names.
*
* @return string[]
*/
public static function getQueryBuilders(): array
{
return app(QueryBuilderLoader::class)->getQueryBuilders();
}

/**
* Get the documentation for each registered query builder.
*
* @return ApidocCollection
*/
public static function getQueryBuilderDocumentation(): ApidocCollection
{
return ApidocCollection::forQueryBuilders(self::getQueryBuilders());
}

public static function getFieldKey()
/**
* Get the key that should be used in requests to fetch the fields.
*
* @return string
*/
public static function getFieldKey(): string
{
return config('apitizer.query_parameters.fields');
}

public static function getSortKey()
/**
* Get the key that should be used in requests to fetch the sorting.
*
* @return string
*/
public static function getSortKey(): string
{
return config('apitizer.query_parameters.sort');
}

public static function getFilterKey()
/**
* Get the key that should be used in requests to fetch the filters.
*
* @return string
*/
public static function getFilterKey(): string
{
return config('apitizer.query_parameters.filters');
}

public static function getLimitKey()
/**
* Get the key that should be used in requests to limit the count of items
* returned in a paginated response.
*
* @return string
*/
public static function getLimitKey(): string
{
return config('apitizer.query_parameters.limit');
}

/**
* Get the mapping of all query params.
*
* @return array
*
* @see Apitizer::getFieldKey
* @see Apitizer::getSortKey
* @see Apitizer::getFilterKey
* @see Apitizer::getLimitKey
*/
public static function getQueryParams()
{
return config('apitizer.query_parameters', []);
}

/**
* Get the route to the documentation.
*
* If documentation generation is disabled, this will return null.
*
* @return null|string
*/
public static function getRouteUrl(): ?string
{
if (! config('apitizer.generate_documentation', true)) {
Expand Down
4 changes: 2 additions & 2 deletions src/Apitizer/Exceptions/ClassFinderException.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public static function composerFileNotFound(string $path)
return new static("Could not find composer file on path [$path]");
}

public static function psr4NotFound()
public static function psr4NotFound(string $composerPath)
{
return new static("Could not find PSR-4 definition in the composer.json");
return new static("Could not find PSR-4 definition in [$composerPath]");
}
}
2 changes: 1 addition & 1 deletion src/Apitizer/Exceptions/InvalidOutputException.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static function castError(Field $field, CastException $e, $row)
}

/**
* Do a best attempt at getting a reference to the object that cause an
* Do a best attempt at getting a reference to the object that caused an
* exception.
*
* The entire row of data cannot be used because there might be sensitive
Expand Down
10 changes: 4 additions & 6 deletions src/Apitizer/Filters/AssociationFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,12 @@
class AssociationFilter
{
/**
* The name of the relation on the query builder.
*
* @var string
* @var string The name of the relation on the query builder.
*/
protected $relation;

/**
* The column name to apply the filtering to.
*
* @var string
* @var string The column name to apply the filtering to.
*/
protected $column;

Expand Down Expand Up @@ -52,6 +48,8 @@ protected function applyFilter(Builder $query, array $values, Relation $relation
// If we filter from the "posts" on the author through the users.id,
// then we don't need to use a subquery/join and we can instead just
// use the posts.author_id directly.
// From: select * from posts where author_id in (select id from users where id in (VALUES))
// To : select * from posts where author_id in (VALUES)
if ($relation instanceof BelongsTo && $foreignJoinKey === $this->column) {
$query->whereIn($localJoinKey, $values);
return;
Expand Down
7 changes: 7 additions & 0 deletions src/Apitizer/Interpreter/QueryInterpreter.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@

class QueryInterpreter
{
/**
* Prepare the query based on the fetch specification.
*
* This will apply selects, filters, and sorting. The `beforeQuery` and
* `afterQuery` hooks are called on the query builder before and after the
* query is built.
*/
public function build(QueryBuilder $queryBuilder, FetchSpec $fetchSpec): Builder
{
$query = $queryBuilder->model()->query();
Expand Down
2 changes: 1 addition & 1 deletion src/Apitizer/Parser/InputParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

/**
* The request parser is responsible for turning the request data that we
* received from the client, into something that can be interpreted by the
* received from the client into something that can be interpreted by the
* rest of the query builder.
*/
class InputParser implements Parser
Expand Down
1 change: 1 addition & 0 deletions src/Apitizer/Parser/RawInput.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

class RawInput
{
// No types are given since this is user input and could therefore be anything.
protected $fields;
protected $filters;
protected $sorts;
Expand Down
1 change: 0 additions & 1 deletion src/Apitizer/Policies/OwnerPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
namespace Apitizer\Policies;

use Apitizer\Types\Concerns\FetchesValueFromRow;
use Apitizer\Types\Field;

/**
* This policy can be used to check if the current object is owned by the
Expand Down
4 changes: 2 additions & 2 deletions src/Apitizer/Policies/Policy.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface Policy
/**
* Check if the value passes the validation.
*
* @param $value the current value that is being evaluated.
* @param mixed $value the current value that is being evaluated.
*
* @param Model|array|mixed $row the current row that is being rendered.
* This value will usually be a Model instance; however, the query builders
Expand All @@ -29,7 +29,7 @@ interface Policy
* @param Field|Association $fieldOrAssoc the field or association instance
* that is currently being rendered. This instance also holds a reference to
* the current query builder if that is needed in the policy. Furthermore,
* the request instance can also be fetched from the query builder.
* the request instance can also be fetched from that query builder.
*/
public function passes($value, $row, $fieldOrAssoc): bool;
}
4 changes: 2 additions & 2 deletions src/Apitizer/Policies/PolicyFailed.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
namespace Apitizer\Policies;

/**
* Used only internally in the Field class to indicate that a policy has failed
* and the value should be filtered out.
* Used only internally to indicate that a policy has failed and the value
* should be filtered out.
*/
class PolicyFailed
{
Expand Down
39 changes: 34 additions & 5 deletions src/Apitizer/QueryBuilderLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,45 @@
use Apitizer\QueryBuilder;
use Apitizer\Support\ComposerNamespaceClassFinder;

/**
* The loader responsible for preparing a list of query builders based on the
* configuration. Query builders can be references directly by name, or by
* using the namespace (assuming PSR-4 and composer are used).
*/
class QueryBuilderLoader
{
/**
* @var string[]
*/
protected $queryBuilders;

public function loadFromConfig()
/**
* Load all query builders based on the config/apitizer.php configuration.
*/
public function loadFromConfig(): void
{
$this->queryBuilders = array_unique(array_merge(
$this->loadIndividualClasses(),
$this->loadNamespaces()
));
}

public function loadIndividualClasses()
/**
* Load all the classes that were registered by name directly.
*
* @return string[]
*/
protected function loadIndividualClasses(): array
{
return config('apitizer.query_builders.classes');
}

public function loadNamespaces()
/**
* Load all query builders from the registered namespaces.
*
* @return string[]
*/
protected function loadNamespaces(): array
{
$classes = [];

Expand All @@ -37,12 +55,23 @@ public function loadNamespaces()
return $classes;
}

public function loadFromNamespace(string $namespace)
/**
* Load the query builders non recursively from a namespace.
*
* @param string $namespace
* @return string[]
*/
protected function loadFromNamespace(string $namespace): array
{
return ComposerNamespaceClassFinder::make($namespace, QueryBuilder::class)->all();
}

public function getQueryBuilders()
/**
* Get a list of fully-qualified namespaces to the registered query builders.
*
* @return string[]
*/
public function getQueryBuilders(): array
{
if (is_null($this->queryBuilders)) {
$this->loadFromConfig();
Expand Down
38 changes: 15 additions & 23 deletions src/Apitizer/Rendering/BasicRenderer.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@

use Apitizer\Policies\PolicyFailed;
use Apitizer\QueryBuilder;
use Illuminate\Support\Arr;

class BasicRenderer implements Renderer
{
public function render(
QueryBuilder $queryBuilder,
$data,
array $selectedFields
): array
public function render(QueryBuilder $queryBuilder, $data, array $selectedFields): array
{
// Check if we're dealing with a single row of data.
if ($this->isSingleDataModel($data) || $this->isNonCollectionObject($data)) {
if ($this->isSingleRowOfData($data)) {
return $this->renderOne($data, $selectedFields);
}

Expand Down Expand Up @@ -51,22 +47,18 @@ protected function renderOne($row, array $selectedFields): array
return $acc;
}

protected function isSingleDataModel($data): bool
/**
* Check if we're dealing with a single row of data or a collection of rows.
*/
protected function isSingleRowOfData($data): bool
{
// Distinguish between arrays as list and arrays as maps.
return is_array($data) && $this->isAssoc($data);
}

protected function isNonCollectionObject($data): bool
{
// Distinguish between e.g. Eloquent objects and Collection objects.
return is_object($data) && !is_iterable($data);
}

private function isAssoc(array $array): bool
{
$keys = array_keys($array);

return array_keys($keys) !== $keys;
return
// Distinguish between arrays as lists of data, or arrays as maps.
// Associative arrays (maps) are considered a single row of data.
(is_array($data) && Arr::isAssoc($data))

// Distinguish between e.g. Eloquent objects and Collection objects.
// Non-iterable objects are considered a single row of data.
|| (is_object($data) && ! is_iterable($data));
}
}
2 changes: 1 addition & 1 deletion src/Apitizer/Support/ComposerNamespaceClassFinder.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public function getIterator()

$composerContent = json_decode(file_get_contents($composerFile), true);
if (! $psr4 = Arr::get($composerContent, 'autoload.psr-4')) {
throw ClassFinderException::psr4NotFound();
throw ClassFinderException::psr4NotFound($composerFile);
}

// Find the first registered psr-4 namespace that starts with
Expand Down
10 changes: 5 additions & 5 deletions src/Apitizer/Support/SchemaValidator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ class SchemaValidator
*
* Requires the query builders to be defined in the config.
*
* @param null|QueryBuilder[] the list of query builders to validate.
* @param null|(string|QueryBuilder)[] the list of query builders to validate.
*/
public function validateAll(array $queryBuilders = null): self
{
$queryBuilders = $queryBuilders ?? array_map(function (string $builderClass) {
return new $builderClass;
}, Apitizer::getQueryBuilders());
foreach ($queryBuilders ?? Apitizer::getQueryBuilders() as $queryBuilder) {
if (is_string($queryBuilder)) {
$queryBuilder = new $queryBuilder;
}

foreach ($queryBuilders as $queryBuilder) {
$this->validate($queryBuilder);
}

Expand Down

0 comments on commit 8bebf2b

Please sign in to comment.