diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4d3bfe..d8847c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,11 @@ Types of changes: `Added`, `Changed`, `Deprecate`, `Removed`, `Fixed`, `Secruity
## [Unreleased]
+## v2.2.0 - 27.02.2024 - New "orderBy" property for default sort order
+
+### Added
+- Added support for a default sort order that can be set with the static property `orderBy`
+
## v2.1.1 - 26.02.2024 - Fixed huge logic issue with allowed functions
### Fixed
diff --git a/app/Http/Controllers/DeliveryNoteController.php b/app/Http/Controllers/DeliveryNoteController.php
index 9c36c56..a79f362 100644
--- a/app/Http/Controllers/DeliveryNoteController.php
+++ b/app/Http/Controllers/DeliveryNoteController.php
@@ -4,7 +4,6 @@
use App\Models\DeliveryNote;
use Framework\Authentication\Auth;
-use Framework\Database\Query\SortOrder;
use Framework\Facades\Http;
use Framework\Routing\BaseController;
use Framework\Routing\ModelControllerInterface;
@@ -19,14 +18,7 @@ public function index(): void
{
Auth::checkRole('Maintainer');
- view(
- 'entities.deliveryNote.index',
- ['deliveryNotes' => DeliveryNote::all(
- DeliveryNote::getQueryBuilder()
- ->orderBy('year', SortOrder::Desc)
- ->orderBy('nr', SortOrder::Desc)
- )]
- );
+ view('entities.deliveryNote.index', ['deliveryNotes' => DeliveryNote::all()]);
}
/**
diff --git a/app/Http/Controllers/InvoiceController.php b/app/Http/Controllers/InvoiceController.php
index c5c660c..1557594 100644
--- a/app/Http/Controllers/InvoiceController.php
+++ b/app/Http/Controllers/InvoiceController.php
@@ -5,7 +5,6 @@
use App\Models\DeliveryNote;
use App\Models\Invoice;
use Framework\Authentication\Auth;
-use Framework\Database\Query\SortOrder;
use Framework\Facades\Http;
use Framework\PDF\PDF;
use Framework\Routing\BaseController;
@@ -21,14 +20,7 @@ public function index(): void
{
Auth::checkRole('Maintainer');
- view(
- 'entities.invoice.index',
- ['invoices' => Invoice::all(
- Invoice::getQueryBuilder()
- ->orderBy('year', SortOrder::Desc)
- ->orderBy('nr', SortOrder::Desc)
- )]
- );
+ view('entities.invoice.index', ['invoices' => Invoice::all()]);
}
/**
diff --git a/app/Models/DeliveryNote.php b/app/Models/DeliveryNote.php
index 756a54c..c1e53ff 100644
--- a/app/Models/DeliveryNote.php
+++ b/app/Models/DeliveryNote.php
@@ -22,6 +22,8 @@ class DeliveryNote extends BaseModel
private const IS_INVOICE_READY = 'isInvoiceReady';
private const INVOICE_ID = 'invoiceId';
+ public static array $orderBy = ['year' => 'desc', 'nr' => 'desc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Invoice.php b/app/Models/Invoice.php
index b87cce9..7915ec9 100644
--- a/app/Models/Invoice.php
+++ b/app/Models/Invoice.php
@@ -17,6 +17,8 @@ class Invoice extends BaseModel
private const RECIPIENT_ID = 'recipientId';
private const IS_PAID = 'isPaid';
+ public static array $orderBy = ['year' => 'desc', 'nr' => 'desc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Plot.php b/app/Models/Plot.php
index 9eae883..7c0a89b 100644
--- a/app/Models/Plot.php
+++ b/app/Models/Plot.php
@@ -16,6 +16,8 @@ class Plot extends BaseModel
private const SUPPLIER_ID = 'supplierId';
private const IS_LOCKED = 'isLocked';
+ public static array $orderBy = ['isLocked' => 'asc', 'nr' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Price.php b/app/Models/Price.php
index 22c4e51..6c73c6a 100644
--- a/app/Models/Price.php
+++ b/app/Models/Price.php
@@ -15,6 +15,8 @@ class Price extends BaseModel
private const PRODUCT_ID = 'productId';
private const RECIPIENT_ID = 'recipientId';
+ public static array $orderBy = ['year' => 'desc', 'productId' => 'asc', 'recipientId' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Product.php b/app/Models/Product.php
index 24b8d08..8b54aa6 100644
--- a/app/Models/Product.php
+++ b/app/Models/Product.php
@@ -13,6 +13,8 @@ class Product extends BaseModel
private const NAME = 'name';
private const IS_DISCONTINUED = 'isDiscontinued';
+ public static array $orderBy = ['isDiscontinued' => 'asc', 'name' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Recipient.php b/app/Models/Recipient.php
index 0eeba5a..1809894 100644
--- a/app/Models/Recipient.php
+++ b/app/Models/Recipient.php
@@ -16,6 +16,8 @@ class Recipient extends BaseModel
private const CITY = 'city';
private const IS_LOCKED = 'isLocked';
+ public static array $orderBy = ['isLocked' => 'asc', 'name' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Setting.php b/app/Models/Setting.php
index 3639507..6081a63 100644
--- a/app/Models/Setting.php
+++ b/app/Models/Setting.php
@@ -13,6 +13,8 @@ class Setting extends BaseModel
private const DESCRIPTION = 'description';
private const VALUE = 'value';
+ public static array $orderBy = ['name' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/Supplier.php b/app/Models/Supplier.php
index 30b2898..9a1ba0d 100644
--- a/app/Models/Supplier.php
+++ b/app/Models/Supplier.php
@@ -15,6 +15,8 @@ class Supplier extends BaseModel
private const HAS_FULL_PAYOUT = 'hasFullPayout';
private const HAS_NO_PAYOUT = 'hasNoPayout';
+ public static array $orderBy = ['isLocked' => 'asc', 'name' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/User.php b/app/Models/User.php
index d8d124a..1c179c1 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -19,6 +19,8 @@ class User extends BaseModel
private const IS_PWD_CHANGE_FORCED = 'isPwdChangeForced';
private const LANGUAGE_ID = 'languageId';
+ public static array $orderBy = ['isLocked' => 'asc', 'username' => 'asc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/app/Models/VolumeDistribution.php b/app/Models/VolumeDistribution.php
index 0300ecd..b21eeee 100644
--- a/app/Models/VolumeDistribution.php
+++ b/app/Models/VolumeDistribution.php
@@ -13,6 +13,8 @@ class VolumeDistribution extends BaseModel
private const PLOT_ID = 'plotId';
private const AMOUNT = 'amount';
+ public static array $orderBy = ['amount' => 'desc'];
+
protected static function new(array $data = []): self
{
return new self($data);
diff --git a/doc/README.md b/doc/README.md
index 333aeee..81f7b91 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -9,6 +9,7 @@
- [Views](#views)
- [Localization](#localization)
- [Models](#models)
+- [BaseModel](#basemodel)
- [Query builder](#query-builder)
- [Migrations](#migrations)
- [Seeders](#seeders)
@@ -47,7 +48,7 @@ There are multiple ways for configuration:
### As function
```PHP
-# Render the view 'about' (/resources/Views/about.php)
+/** Render the view 'about' (/resources/Views/about.php) */
Router::addFn('about', fn() => view('about'));
```
@@ -56,7 +57,7 @@ Router::addFn('about', fn() => view('about'));
The controller class must implement the [`Framework\Routing\ControllerInterface`](../framework/Routing/ControllerInterface.php).
```PHP
-# The function 'execute()' is called in the AboutController class
+/** The function 'execute()' is called in the AboutController class */
Router::addController('about', new AboutController());
```
@@ -66,7 +67,7 @@ Router::addController('about', new AboutController());
The controller must implement the [`Framework\Routing\ModelControllerInterface`](../framework/Routing/ModelControllerInterface.php).
```PHP
-# Routes: user, user/show, user/create, user/store, etc.
+/** Routes: user, user/show, user/create, user/store, etc. */
Router::addResource('user', new UserController());
```
@@ -129,24 +130,43 @@ return [
## Models
> **Hint:** You can use the bioman CLI to create a new empty command: `make:model`
-### BaseModel functions
-It is recommended to use the BaseModel and name the identifier column `id`. This ways the following functions can be used for every model:
+A model can be used as a wrapper for a database table or to encapsulte application logic.
+If you extend your class from the [`BaseModel`](../framework/Database/BaseModel.php), you get access to helper functions.
+These already implement deletion, finding multiple or single instances, etc.
+Your table scheme should use the column `id` as primary key in order to use the BaseModel.
+
+See [BaseModel](#basemodel)
+
+-------------------------------------------------------------
+
+## BaseModel
+The [`BaseModel`](../framework/Database/BaseModel.php) provides logic to simplify the usage of a model that is stored in the database.
+
+The BaseModel assumes that the identifier column is named `id`.
+
+### Functions
```PHP
-# Get an empty query builder
-public static function getQueryBuilder(): WhereQueryBuilder
+/** Get an empty query builder */
+public static function getQueryBuilder(): WhereQueryBuilder;
-# Get an array of models
+/** Get an array of models */
public static function all(WhereQueryBuilder $query = null): array;
-# Get the first model that matches the given query
+/** Get the first model that matches the given query */
public static function find(WhereQueryBuilder $query): self;
-# Get the model with the given id
+/** Get the model with the given id */
public static function findById(int $id): self;
-# Delete the model with the given id
+/** Delete the model with the given id */
public static function delete(int $id): void;
+
+/** Set the given fields from the the eponymous HTTP parameters */
+public function setFromHttpParams(array $fields): self;
+
+/** Set the given field from the the eponymous HTTP parameter */
+public function setFromHttpParam(string $field, string $param = null): self;
```
**Example model**
@@ -157,43 +177,6 @@ use Framework\Database\Database;
class Role extends BaseModel
```
-### Allow functions
-The allow functions are optional functions that can be added to a model for doing checks before the edit or delete operation.
-Possible checks could be permission checks to ensure that the current user is allowed to delete that entity. Another useful check is a dependency checks. Here you can check if a invoice is aleady paid and prevent the edit and delete operation in that case.
-
-**Signatures**
-```PHP
- public function allowEdit(): bool;
-
- public function allowDelete(): bool;
-```
-
-The `allowEdit` function gets executed if the `save()` function is called.
-The `allowDelete` function gets executed if the `delete($id)` function is called.
-
-**Examples**
-```PHP
- public function allowEdit(): bool
- {
- return match (true) {
- $this->getIsPaid() => false,
- // More checks...
- default => true,
- };
- }
-
- public function allowDelete(): bool
- {
- if ($this->getIsPaid()) {
- return false;
- }
-
- // More checks...
-
- return true;
- }
-```
-
### Table name
A model class is a wrapper for a database table. The name of the table must be the plural of the class name so that the model can resolve the table name.
@@ -227,6 +210,55 @@ public function getPassword(): ?string
}
```
+### Allow functions
+The allow functions are optional functions that can be added to a model for doing checks before the edit or delete operation.
+Possible checks could be permission checks to ensure that the current user is allowed to delete that entity. Another useful check is a dependency checks. Here you can check if a invoice is aleady paid and prevent the edit and delete operation in that case.
+
+**Signatures**
+```PHP
+/* Gets executed if the `save()` function is called */
+public function allowEdit(): bool;
+
+/** Gets executed if the `delete($id)` function is called */
+public function allowDelete(): bool;
+```
+
+**Examples**
+```PHP
+public function allowEdit(): bool
+{
+ return match (true) {
+ $this->getIsPaid() => false,
+ // More checks...
+ default => true,
+ };
+}
+
+public function allowDelete(): bool
+{
+ if ($this->getIsPaid()) {
+ return false;
+ }
+
+ // More checks...
+
+ return true;
+}
+```
+
+### Default sort order
+You can use the [(Where)QueryBuilder](#query-builder) to set the sort order when calling `Model::all(...)`. This way you must set that order possibly on multiple locations in your source code.
+
+You can use the static property `orderBy` to set a default order.
+It will be used if no other sort order is passed via `QueryBuilder->orderBy(...)`.
+
+**Example**
+```PHP
+class Plot extends BaseModel
+{
+ public static array $orderBy = ['isLocked' => 'asc', 'nr' => 'asc'];
+```
+
-------------------------------------------------------------
## Query builder
@@ -285,7 +317,7 @@ return new class extends Migration
{
public function run(): void
{
- # code
+ // code
...
```
@@ -310,7 +342,7 @@ class UserSeeder extends Seeder implements SeederInterface
{
public function run(): void
{
- # code
+ // code
...
```
diff --git a/framework/Database/BaseModel.php b/framework/Database/BaseModel.php
index 49a680b..348cbba 100644
--- a/framework/Database/BaseModel.php
+++ b/framework/Database/BaseModel.php
@@ -5,6 +5,7 @@
use Exception;
use Framework\Database\Query\ColType;
use Framework\Database\Query\Condition;
+use Framework\Database\Query\SortOrder;
use Framework\Facades\Http;
/**
@@ -16,6 +17,8 @@
*/
abstract class BaseModel
{
+ public static array $orderBy = [];
+
protected const ID = 'id';
public function __construct(
@@ -48,14 +51,23 @@ protected function checkAllowEdit(): void
public static function getQueryBuilder(): WhereQueryBuilder
{
- return new WhereQueryBuilder(self::getTableName());
+ return new WhereQueryBuilder(static::getTableName());
}
public static function all(WhereQueryBuilder $query = null): array
{
if ($query === null) {
- $query = self::getQueryBuilder();
+ $query = static::getQueryBuilder();
}
+
+ // Use default sort order if it is defined in the model and if no sort order is set via the query builder
+ if (!$query->hasOrderBySection() && count(static::$orderBy) > 0) {
+ foreach (static::$orderBy as $field => $sortOrderStr) {
+ $sortOrder = strtolower($sortOrderStr) === 'asc' ? SortOrder::Asc : SortOrder::Desc;
+ $query->orderBy($field, $sortOrder);
+ }
+ }
+
$dataSet = Database::executeBuilder($query);
if ($dataSet === false) {
@@ -71,7 +83,7 @@ public static function all(WhereQueryBuilder $query = null): array
public static function find(WhereQueryBuilder $query): self
{
- $results = self::all($query);
+ $results = static::all($query);
if ($results === []) {
return static::new();
}
@@ -80,12 +92,12 @@ public static function find(WhereQueryBuilder $query): self
public static function findById(int $id): self
{
- return self::find(self::getQueryBuilder()->where(ColType::Int, 'id', Condition::Equal, $id));
+ return static::find(static::getQueryBuilder()->where(ColType::Int, 'id', Condition::Equal, $id));
}
public static function delete(int $id): void
{
- $model = self::findById($id);
+ $model = static::findById($id);
if ($model->getId() === null) {
return;
}
@@ -96,14 +108,14 @@ public static function delete(int $id): void
}
}
- Database::prepared('DELETE FROM ' . self::getTableName() . ' WHERE id=?', 'i', $id);
+ Database::prepared('DELETE FROM ' . static::getTableName() . ' WHERE id=?', 'i', $id);
}
/* Getter */
public function getId(): ?int
{
- return $this->getDataIntOrNull(self::ID);
+ return $this->getDataIntOrNull(static::ID);
}
/* Magical getter and setter */
diff --git a/framework/Database/QueryBuilder.php b/framework/Database/QueryBuilder.php
index 3507a22..4003c1e 100644
--- a/framework/Database/QueryBuilder.php
+++ b/framework/Database/QueryBuilder.php
@@ -105,6 +105,11 @@ public function getColTypes(): string
return $types;
}
+ public function hasOrderBySection(): bool
+ {
+ return $this->getOrderByStr() !== '';
+ }
+
private function getWhereStr(): string
{
if ($this->isWhereEmpty()) {
diff --git a/framework/Database/WhereQueryBuilder.php b/framework/Database/WhereQueryBuilder.php
index 84f30d8..3d1fa71 100644
--- a/framework/Database/WhereQueryBuilder.php
+++ b/framework/Database/WhereQueryBuilder.php
@@ -54,4 +54,9 @@ public function getColTypes(): string
{
return $this->queryBuilder->getColTypes();
}
+
+ public function hasOrderBySection(): bool
+ {
+ return $this->queryBuilder->hasOrderBySection();
+ }
}
\ No newline at end of file
diff --git a/resources/Views/Components/invoiceSelect.php b/resources/Views/Components/invoiceSelect.php
index 1fe2989..98defdb 100644
--- a/resources/Views/Components/invoiceSelect.php
+++ b/resources/Views/Components/invoiceSelect.php
@@ -1,22 +1,16 @@
Id that should be selected
-$invoices = Invoice::all(Invoice::getQueryBuilder()
- ->orderBy('year', SortOrder::Desc)
- ->orderBy('nr', SortOrder::Desc)
-);
-
?>