Skip to content

Commit

Permalink
fix(smart-collection): add support of smart-relationship (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasalexandre9 authored Jan 11, 2023
1 parent 127b8a6 commit 9e680f5
Show file tree
Hide file tree
Showing 9 changed files with 712 additions and 31 deletions.
13 changes: 8 additions & 5 deletions src/Services/Concerns/ForestCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use ForestAdmin\LaravelForestAdmin\Exceptions\ForestException;
use ForestAdmin\LaravelForestAdmin\Facades\ForestSchema;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartAction;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartCollection;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartField;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartRelationship;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartSegment;
Expand Down Expand Up @@ -136,22 +137,24 @@ public function smartRelationship(array $attributes): SmartRelationship
}

/**
* @return Model
* @return Model|SmartCollection
*/
public function handleSmartFields(): Model
public function handleSmartFields()
{
$smartFields = ForestSchema::getSmartFields(strtolower(class_basename($this)));
foreach ($smartFields as $smartField) {
$this->{$smartField['field']} = call_user_func($this->{$smartField['field']}()->get);
if (method_exists($this, $smartField['field'])) {
$this->{$smartField['field']} = call_user_func($this->{$smartField['field']}()->get);
}
}

return $this;
}

/**
* @return Model
* @return Model|SmartCollection
*/
public function handleSmartRelationships(): Model
public function handleSmartRelationships()
{
$smartRelationships = ForestSchema::getSmartRelationships(strtolower(class_basename($this)));
foreach ($smartRelationships as $smartRelationship) {
Expand Down
29 changes: 26 additions & 3 deletions src/Services/JsonApiResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use ForestAdmin\LaravelForestAdmin\Facades\ForestSchema;
use ForestAdmin\LaravelForestAdmin\Serializer\JsonApiSerializer;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartCollection;
use ForestAdmin\LaravelForestAdmin\Transformers\BaseTransformer;
use ForestAdmin\LaravelForestAdmin\Transformers\SmartCollectionTransformer;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\JsonResponse;
Expand Down Expand Up @@ -46,19 +48,25 @@ public function __construct()
*/
public function render($class, string $name, array $meta = [])
{
$reflectionClass = new \ReflectionClass(ForestSchema::getClass($name));
$data = $reflectionClass->isSubclassOf(SmartCollection::class)
? $this->hydrateData($class, $reflectionClass->getName())
: $class;

$this->fractal->setSerializer(new JsonApiSerializer(config('app.url')));
$transformer = app()->make(BaseTransformer::class);
$transformer->setAvailableIncludes(ForestSchema::getRelatedData($name));

if (is_array($class) || $this->isCollection($class)) {
$resource = new Collection($class, $transformer, $name);
$resource = new Collection($data, $transformer, $name);
} elseif ($this->isPaginator($class)) {
$resource = new Collection($class->getCollection(), $transformer, $name);
$data = $this->isPaginator($data) ? $data->getCollection() : $data;
$resource = new Collection($data, $transformer, $name);
if (request()->has('search')) {
$resource->setMeta($this->searchDecorator($resource->getData(), request()->get('search')));
}
} else {
$resource = new Item($class, $transformer, $name);
$resource = new Item($data, $transformer, $name);
}

if ($meta) {
Expand Down Expand Up @@ -131,4 +139,19 @@ protected function searchDecorator(BaseCollection $items, $searchValue): array

return $decorator;
}

protected function hydrateData($class, $className)
{
if (is_array($class) || $this->isCollection($class) || $this->isPaginator($class)) {
$records = [];
$collections = $this->isPaginator($class) ? $class->getCollection() : $class;
foreach ($collections as $value) {
$records[] = $className::hydrate($value);
}

return $records;
} else {
return $className::hydrate($class);
}
}
}
95 changes: 90 additions & 5 deletions src/Services/SmartFeatures/SmartCollection.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@

namespace ForestAdmin\LaravelForestAdmin\Services\SmartFeatures;

use Closure;
use ForestAdmin\LaravelForestAdmin\Exceptions\ForestException;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Str;
use phpDocumentor\Reflection\DocBlock\Tags\Param;

/**
* Class SmartCollection
Expand All @@ -33,6 +30,11 @@ class SmartCollection
*/
protected bool $is_searchable = false;

/**
* @var array
*/
protected array $relations = [];

/**
* @return Collection
*/
Expand All @@ -48,7 +50,19 @@ public function serializeFields(): array
{
$this->isValid();

return $this->fields()->map(fn($item) => $item->serialize())->all();
$methods = (new \ReflectionClass($this))->getMethods(\ReflectionMethod::IS_PUBLIC);
$smartRelationships = new Collection();

foreach ($methods as $method) {
if (($returnType = $method->getReturnType()) && $returnType->getName() === SmartRelationship::class && $method->getName() !== lcfirst(class_basename(SmartRelationship::class))) {
$smartRelationships->push($this->{$method->getName()}()->serialize());
}
}

return $this->fields()
->map(fn($item) => $item->serialize())
->merge($smartRelationships)
->all();
}

/**
Expand All @@ -59,6 +73,7 @@ public function serialize(): array
return [
'name' => $this->name,
'name_old' => $this->name,
'class' => static::class,
'icon' => null,
'is_read_only' => $this->is_read_only,
'is_virtual' => true,
Expand All @@ -84,4 +99,74 @@ public function isValid()

return true;
}

/**
* @param $record
* @return static
*/
public static function hydrate($record): self
{
if ($record instanceof Model) {
$record = $record->toArray();
} else {
$record = (array) $record;
}
$object = new static();
foreach ($object->getAttributes() as $attribute) {
if (isset($record[$attribute])) {
$object->$attribute = $record[$attribute];
}
}

return $object;
}

/**
* @return string
*/
public function getName(): string
{
return $this->name;
}

/**
* @return array
*/
public function getAttributes(): array
{
return $this->fields()->map(fn(SmartField $field) => $field->getField())->toArray();
}

/**
* @param $relation
* @param $value
* @return $this
*/
public function setRelation($relation, $value): SmartCollection
{
$this->relations[$relation] = $value;

return $this;
}

/**
* @return array
*/
public function getRelations(): array
{
return $this->relations;
}

/**
* @return array
*/
public function attributesToArray(): array
{
$record = [];
foreach ($this->getAttributes() as $attribute) {
$record[$attribute] = $this->$attribute ?? null;
}

return $record;
}
}
5 changes: 3 additions & 2 deletions src/Transformers/BaseTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace ForestAdmin\LaravelForestAdmin\Transformers;

use ForestAdmin\LaravelForestAdmin\Facades\ForestSchema;
use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartCollection;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use League\Fractal\TransformerAbstract;
Expand Down Expand Up @@ -37,10 +38,10 @@ public function __call($method, $arguments)
}

/**
* @param Model $model
* @param Model|SmartCollection $model
* @return mixed
*/
public function transform(Model $model)
public function transform($model)
{
if (method_exists($model, 'handleSmartFields')) {
$model->handleSmartFields()->handleSmartRelationships();
Expand Down
5 changes: 3 additions & 2 deletions src/Transformers/ChildTransformer.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace ForestAdmin\LaravelForestAdmin\Transformers;

use ForestAdmin\LaravelForestAdmin\Services\SmartFeatures\SmartCollection;
use Illuminate\Database\Eloquent\Model;
use League\Fractal\TransformerAbstract;

Expand All @@ -15,10 +16,10 @@
class ChildTransformer extends TransformerAbstract
{
/**
* @param Model $model
* @param Model|SmartCollection $model
* @return array
*/
public function transform(Model $model)
public function transform($model)
{
if (method_exists($model, 'handleSmartFields')) {
$model->handleSmartFields()->handleSmartRelationships();
Expand Down
59 changes: 59 additions & 0 deletions tests/Unit/JsonApiResponseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,65 @@ public function testRender(): void
$this->assertEquals($smartBookstores, $render['data']['relationships']['smartBookstores']);
}

/**
* @return void
* @throws BindingResolutionException
* @throws \JsonException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function testRenderWithSmartCollection(): void
{
$jsonApi = new JsonApiResponse();
App::shouldReceive('basePath')->andReturn(null);
File::shouldReceive('get')->andReturn($this->fakeSchema());
$book = Book::limit(3)->get();
$render = $jsonApi->render($book, 'comic');

$this->assertIsArray($render);
$this->assertArrayHasKey('data', $render);
$this->assertArrayHasKey('type', $render['data'][0]);
$this->assertArrayHasKey('id', $render['data'][0]);
$this->assertArrayHasKey('attributes', $render['data'][0]);
$this->assertArrayHasKey('label', $render['data'][0]['attributes']);
$this->assertArrayHasKey('created_at', $render['data'][0]['attributes']);
}

/**
* @return void
* @throws BindingResolutionException
* @throws \JsonException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function testRenderSmartCollectionWithSmartRelation(): void
{
$jsonApi = new JsonApiResponse();
App::shouldReceive('basePath')->andReturn(null);
File::shouldReceive('get')->andReturn($this->fakeSchema());
$book = Book::first();
$render = $jsonApi->render($book, 'comic');

$this->assertIsArray($render);
$this->assertArrayHasKey('data', $render);
$this->assertArrayHasKey('type', $render['data']);
$this->assertArrayHasKey('id', $render['data']);
$this->assertArrayHasKey('attributes', $render['data']);
$this->assertArrayHasKey('label', $render['data']['attributes']);
$this->assertArrayHasKey('created_at', $render['data']['attributes']);
$this->assertArrayHasKey('relationships', $render['data']);
// BelongsTo relation
$this->assertArrayHasKey('category', $render['data']['relationships']);
$this->assertArrayHasKey('data', $render['data']['relationships']['category']);
$this->assertArrayHasKey('type', $render['data']['relationships']['category']['data']);
$this->assertArrayHasKey('id', $render['data']['relationships']['category']['data']);
// HasMany relation
$this->assertArrayHasKey('bookStores', $render['data']['relationships']);
$this->assertArrayHasKey('links', $render['data']['relationships']['bookStores']);
$this->assertArrayHasKey('related', $render['data']['relationships']['bookStores']['links']);
$this->assertArrayHasKey('href', $render['data']['relationships']['bookStores']['links']['related']);
}

/**
* @return void
* @throws BindingResolutionException
Expand Down
Loading

0 comments on commit 9e680f5

Please sign in to comment.