Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 91 additions & 9 deletions src/Http/Resources/TransformRelationToResource.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,24 @@
use Cronqvist\Api\Services\Helpers\GuessForModel;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Resources\Json\ResourceCollection;
use Illuminate\Http\Resources\MissingValue;
use Illuminate\Support\Str;
use Spatie\QueryBuilder\QueryBuilderRequest;

trait TransformRelationToResource
{
use GuessForModel;

private ?ApiResource $parentResource = null;
private ?string $requestedAs = null;

/**
* Override the mapping of a Model with its Resource
*
* @var array
*/
protected $modelResourceMap = [];
protected array $modelResourceMap = [];

/**
* Transform all loaded relations with its own related resource class
Expand All @@ -40,19 +46,22 @@ protected function transformRelations($data)
/**
* Transform a relation with its own related resource class
*
* @param string $relation
* @return mixed
* @param Model|Collection $relation
* @return ApiResource|ResourceCollection|mixed
*/
protected function transformRelation($relation)
{
if(empty($relation)) return $relation;
if(empty($relation)) {
return $relation;
}

if($relation instanceof Model) {
if($resource = $this->getResourceClassFor($relation)) {
return new $resource($relation);
}
} else if($relation instanceof Collection) {
if($relation->count() && $resource = $this->getResourceClassFor($relation->first())) {
/** @var ApiResource $resource */
return $resource::collection($relation);
}
}
Expand All @@ -62,10 +71,10 @@ protected function transformRelation($relation)
/**
* Get the resource class based on the model, if it exist.
*
* @param \Illuminate\Database\Eloquent\Model $model
* @param Model $model
* @return string
*/
protected function getResourceClassFor(Model $model)
protected function getResourceClassFor(Model $model): ?string
{
$modelClass = get_class($model);
if(isset($this->modelResourceMap[$modelClass])) {
Expand All @@ -79,12 +88,85 @@ protected function getResourceClassFor(Model $model)
* Retrieve a relationship if it has been loaded and map it to its Resource class.
*
* @param string $relationship
* @return \Cronqvist\Api\Http\Resources\ApiResource|\Illuminate\Http\Resources\MissingValue
* @return ApiResource|ResourceCollection|MissingValue|mixed
*/
protected function whenLoadedToResource($relationship)
protected function whenLoadedToResource(string $relationship)
{
return $this->whenLoaded($relationship, function() use($relationship) {
return $this->transformRelation($this->resource->getRelation($relationship));
$resource = $this->transformRelation($this->resource->getRelation($relationship));
if($resource instanceof ApiResource) {
$resource->setParent($this, $relationship);
} else if($resource instanceof ResourceCollection) {
$resource->each(fn(ApiResource $res) => $res->setParent($this, $relationship));
}
return $resource;
});
}

public function setParent(ApiResource $parentResource, string $requestedAs):void {
$this->parentResource = $parentResource;
$this->requestedAs = $requestedAs;
}

protected function includePath(): array
{
$stack=[];
$resource = $this;
do {
$stack[] = $resource->requestedAs;
} while($resource = $resource->parentResource);
array_pop($stack); //remove root resource
return array_reverse($stack);
}

protected function globalIncludes(): \Illuminate\Support\Collection
{
return QueryBuilderRequest::createFrom(request())->includes();
}

protected function localIncludes(): array
{
$result = [];
$path = $this->includePath();
$pathLength = count($path);
foreach ($this->globalIncludes() as $include){
$include = explode('.',$include);
for ($matches=0; $matches<$pathLength; $matches++) {
$pathPart = $path[$matches];
if($pathPart !== $include[$matches]){
break;
}
}
$res = array_slice($include, $matches);
if(count($res) && !in_array($res, $result, true)){
$result[] = $res;
}
}
return $result;
}
protected function directIncludes(): array
{
return array_unique(array_map(static fn($e)=>$e[0],$this->localIncludes()));
}

protected function whenIncluded($relationship, $callback)
{
if(in_array($relationship, $this->directIncludes(), true)) {
return $callback();
}
return new MissingValue;
}

protected function whenIncludedToResource($relationship)
{
return $this->whenIncluded($relationship, function() use($relationship) {
$resource = $this->transformRelation($this->resource->getRelation($relationship));
if($resource instanceof ApiResource) {
$resource->setParent($this, $relationship);
} else if($resource instanceof ResourceCollection) {
$resource->each(fn($res) => $res->setParent($this, $relationship));
}
return $resource;
});
}
}
18 changes: 18 additions & 0 deletions src/Services/QueryBuilder/ApiIncludes.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Cronqvist\Api\Services\QueryBuilder;

use Cronqvist\Api\Services\QueryBuilder\Includes\IncludedDummy;
use Illuminate\Support\Collection;
use Spatie\QueryBuilder\AllowedInclude;

class ApiIncludes extends AllowedInclude
{

public static function dummy(string $name, $internalName = null): Collection
{
return collect([
new static($name, new IncludedDummy(), $internalName),
]);
}
}
14 changes: 14 additions & 0 deletions src/Services/QueryBuilder/Includes/IncludedDummy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace Cronqvist\Api\Services\QueryBuilder\Includes;

use Illuminate\Database\Eloquent\Builder;
use Spatie\QueryBuilder\Includes\IncludeInterface;

class IncludedDummy implements IncludeInterface
{
public function __invoke(Builder $query, string $include)
{

}
}