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) -); - ?>