diff --git a/src/Collection/Helper/ColumnCreator.php b/src/Collection/Helper/ColumnCreator.php index e4f1ec5..70c62f9 100644 --- a/src/Collection/Helper/ColumnCreator.php +++ b/src/Collection/Helper/ColumnCreator.php @@ -24,13 +24,13 @@ class ColumnCreator { - public static function create(string $type, string $key, bool $visible): IColumn + public static function create(string $type, string $key, bool $visible, bool $sortable): IColumn { $keysMap = [ - 'email' => new EmailColumn(key: $key, name: $key, visible: $visible), - 'phone' => new PhoneColumn(key: $key, name: $key, visible: $visible), - 'url' => new UrlColumn(key: $key, name: $key, visible: $visible), - 'video' => new VideoLinkColumn(key: $key, name: $key, visible: $visible), + 'email' => new EmailColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), + 'phone' => new PhoneColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), + 'url' => new UrlColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), + 'video' => new VideoLinkColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), ]; $columnByKey = null; @@ -47,32 +47,32 @@ public static function create(string $type, string $key, bool $visible): IColumn Types::DECIMAL, Types::GUID, Types::STRING, - Types::TEXT => $columnByKey ?: new StringColumn(key: $key, name: $key, visible: $visible), + Types::TEXT => $columnByKey ?: new StringColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), - Types::BLOB => new BlobColumn(key: $key, name: $key, visible: $visible), + Types::BLOB => new BlobColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), - Types::BOOLEAN => new BooleanColumn(key: $key, name: $key, visible: $visible), + Types::BOOLEAN => new BooleanColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), Types::DATE_MUTABLE, - Types::DATE_IMMUTABLE => new DateColumn(key: $key, name: $key, visible: $visible), - Types::DATEINTERVAL => new DateTimeIntervalColumn(key: $key, name: $key, visible: $visible), + Types::DATE_IMMUTABLE => new DateColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), + Types::DATEINTERVAL => new DateTimeIntervalColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), Types::DATETIME_MUTABLE, Types::DATETIME_IMMUTABLE, Types::DATETIMETZ_MUTABLE, - Types::DATETIMETZ_IMMUTABLE => new DateTimeColumn(key: $key, name: $key, visible: $visible), + Types::DATETIMETZ_IMMUTABLE => new DateTimeColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), Types::FLOAT, Types::INTEGER, - Types::SMALLINT => new NumericColumn(key: $key, name: $key, visible: $visible), + Types::SMALLINT => new NumericColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), - Types::JSON => new JsonColumn(key: $key, name: $key, visible: $visible), - Types::SIMPLE_ARRAY => new ArrayColumn(key: $key, name: $key, visible: $visible), + Types::JSON => new JsonColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), + Types::SIMPLE_ARRAY => new ArrayColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), Types::TIME_MUTABLE, - Types::TIME_IMMUTABLE => new TimeColumn(key: $key, name: $key, visible: $visible), + Types::TIME_IMMUTABLE => new TimeColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), - default => new UnknownColumn(key: $key, name: $key, visible: $visible), + default => new UnknownColumn(key: $key, name: $key, sortable: $sortable, visible: $visible), }; } } \ No newline at end of file diff --git a/src/Collection/ICollectionRecipe.php b/src/Collection/ICollectionRecipe.php index 399198a..ea3faaf 100644 --- a/src/Collection/ICollectionRecipe.php +++ b/src/Collection/ICollectionRecipe.php @@ -15,22 +15,16 @@ public function source(): string; /** @return string */ public function key(): string; - /** - * @throws \Megio\Collection\Exception\CollectionException - */ + /** @throws \Megio\Collection\Exception\CollectionException */ public function read(ReadBuilder $builder, RecipeRequest $request): ReadBuilder; - /** - * @throws \Megio\Collection\Exception\CollectionException - */ + /** @throws \Megio\Collection\Exception\CollectionException */ public function readAll(ReadBuilder $builder, RecipeRequest $request): ReadBuilder; public function create(WriteBuilder $builder, RecipeRequest $request): WriteBuilder; public function update(WriteBuilder $builder, RecipeRequest $request): WriteBuilder; - /** - * @throws \Megio\Collection\Exception\CollectionException - */ + /** @throws \Megio\Collection\Exception\CollectionException */ public function getEntityMetadata(): RecipeEntityMetadata; } \ No newline at end of file diff --git a/src/Collection/ReadBuilder/ReadBuilder.php b/src/Collection/ReadBuilder/ReadBuilder.php index 6bbaf00..d465b92 100644 --- a/src/Collection/ReadBuilder/ReadBuilder.php +++ b/src/Collection/ReadBuilder/ReadBuilder.php @@ -84,13 +84,14 @@ public function buildByDbSchema(array $exclude = [], bool $persist = false): sel { $this->addIdColumnIfNotExists(); + $sortableCols = ['id', 'createdAt', 'updatedAt']; $invisibleCols = ['id', 'createdAt', 'updatedAt']; $ignored = array_merge($exclude, ['id']); foreach ($this->dbSchema->getUnionColumns() as $column) { if (!in_array($column['name'], $ignored)) { $visible = !in_array($column['name'], $invisibleCols); - $col = ColumnCreator::create($column['type'], $column['name'], $visible); + $col = ColumnCreator::create($column['type'], $column['name'], $visible, in_array($column['name'], $sortableCols)); $this->columns[$col->getKey()] = $col; } } @@ -231,7 +232,7 @@ protected function addIdColumnIfNotExists(): void { if (!array_key_exists('id', $this->columns)) { $this->columns = array_merge([ - 'id' => new StringColumn(key: 'id', name: 'ID', visible: false), + 'id' => new StringColumn(key: 'id', name: 'ID', sortable: true, visible: false), ], $this->columns); } } diff --git a/src/Http/Request/Collection/ReadAllRequest.php b/src/Http/Request/Collection/ReadAllRequest.php index be13e41..efac56e 100644 --- a/src/Http/Request/Collection/ReadAllRequest.php +++ b/src/Http/Request/Collection/ReadAllRequest.php @@ -6,6 +6,7 @@ use Doctrine\ORM\AbstractQuery; use Doctrine\ORM\Tools\Pagination\Paginator; use Megio\Collection\Exception\CollectionException; +use Megio\Collection\ReadBuilder\Column\Base\IColumn; use Megio\Collection\ReadBuilder\ReadBuilder; use Megio\Collection\ReadBuilder\ReadBuilderEvent; use Megio\Collection\RecipeFinder; @@ -30,9 +31,10 @@ public function __construct( { } - public function schema(): array + public function schema(array $data): array { - $recipeKeys = array_map(fn($r) => $r->key(), $this->recipeFinder->load()->getAll()); + $recipes = $this->recipeFinder->load()->getAll(); + $recipeKeys = array_map(fn($r) => $r->key(), $recipes); return [ 'recipe' => Expect::anyOf(...$recipeKeys)->required(), @@ -43,7 +45,7 @@ public function schema(): array 'orderBy' => Expect::arrayOf(Expect::structure([ 'col' => Expect::string()->required(), 'desc' => Expect::bool()->required() - ])->castTo('array'))->min(1)->default([['col' => 'createdAt', 'desc' => true]]), + ])->castTo('array'))->min(0)->default([]), 'custom_data' => Expect::arrayOf('int|float|string|bool|null|array', 'string')->nullable()->default([]), ]; } @@ -69,7 +71,6 @@ public function process(array $data): Response return $this->error([$e->getMessage()]); } - /** @noinspection DuplicatedCode */ if ($builder->countFields() === 1) { return $this->error(["Collection '{$data['recipe']}' has no readable fields"]); @@ -94,8 +95,19 @@ public function process(array $data): Response ->setFirstResult(($data['currentPage'] - 1) * $data['itemsPerPage']) ->setMaxResults($data['itemsPerPage']); + // Sortable columns + $sortable = array_filter($builder->getColumns(), fn(IColumn $col) => $col->isSortable()); + $sortableKeys = array_map(fn(IColumn $col) => $col->getKey(), $sortable); + + // Order by only by sortable columns foreach ($data['orderBy'] as $param) { - $qb->addOrderBy("entity.{$param['col']}", $param['desc'] ? 'DESC' : 'ASC'); + if (in_array($param['col'], $sortableKeys)) { + $qb->addOrderBy("entity.{$param['col']}", $param['desc'] ? 'DESC' : 'ASC'); + } + } + + if (!in_array('id', array_column($data['orderBy'], 'col'))) { + $qb->addOrderBy('entity.id', 'ASC'); } $query = $qb->getQuery()->setHydrationMode(AbstractQuery::HYDRATE_ARRAY); diff --git a/src/Recipe/AdminRecipe.php b/src/Recipe/AdminRecipe.php index ba8d340..8ee3031 100644 --- a/src/Recipe/AdminRecipe.php +++ b/src/Recipe/AdminRecipe.php @@ -3,6 +3,7 @@ namespace Megio\Recipe; +use Megio\Collection\ReadBuilder\Column\EmailColumn; use Megio\Collection\ReadBuilder\ReadBuilder; use Megio\Collection\RecipeRequest; use Megio\Collection\WriteBuilder\Field\Base\EmptyValue; @@ -12,7 +13,6 @@ use Megio\Collection\WriteBuilder\Rule\RequiredRule; use Megio\Collection\CollectionRecipe; use Megio\Database\Entity\Admin; -use Symfony\Component\HttpFoundation\Request; class AdminRecipe extends CollectionRecipe { @@ -33,7 +33,8 @@ public function read(ReadBuilder $builder, RecipeRequest $request): ReadBuilder public function readAll(ReadBuilder $builder, RecipeRequest $request): ReadBuilder { - return $builder->buildByDbSchema(['password']); + return $builder->buildByDbSchema(['password'], persist: true) + ->add(new EmailColumn('email', 'E-mail', true)); } public function create(WriteBuilder $builder, RecipeRequest $request): WriteBuilder