Skip to content
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Calling create() resets model state to ensure clean inserts
- Database-generated primary keys are now synced into the model after save()
- Models are hydrated only on fetch operations, not implicitly via getters
- `DbModel::save()` now automatically applies timestamps when the model uses the `HasTimestamps` trait
- `SoftDeletes` now uses model timestamp format when available (datetime/unix) for `deleted_at`

### Fixed
- PHP 8.1 compatibility: Fixed null parameter deprecations in `explode()`, `parse_url()`, and `str_replace()`
Expand All @@ -48,6 +50,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Support for force mode and specific task execution
- Automatic cleanup of stale locks (older than 24 hours)
- Full documentation in `docs/cron-scheduler.md`
- **Opt-in Model Timestamps**: Introduced `HasTimestamps` trait for `DbModel`:
- Automatically sets `created_at` on insert
- Automatically sets `updated_at` on insert and update
- Supports custom timestamp column names via model constants (`CREATED_AT`, `UPDATED_AT`)
- Supports datetime and unix timestamp formats via `TIMESTAMP_TYPE`

### Removed
- Support for PHP 7.3 and earlier versions
Expand Down
4 changes: 4 additions & 0 deletions src/Model/DbModel.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ public function create(): self
*/
public function save(): bool
{
if (method_exists($this, 'touchTimestamps')) {
$this->touchTimestamps();
}

$this->syncAttributesToOrm();

$result = $this->getOrmInstance()->save();
Expand Down
2 changes: 1 addition & 1 deletion src/Model/Helpers/model.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@

/**
* Gets the model instance
* @template T of Model
* @param class-string<T> $modelClass
* @return T
* @throws ModelException
* @template T of Model
*/
function model(string $modelClass): Model
{
Expand Down
126 changes: 126 additions & 0 deletions src/Model/Traits/HasTimestamps.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

/**
* Quantum PHP Framework
*
* An open source software development framework for PHP
*
* @package Quantum
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 3.0.0
*/

namespace Quantum\Model\Traits;

/**
* Trait HasTimestamps
*
* @property array $attributes
* @property string $idColumn
*/
trait HasTimestamps
{
/**
* Column name for created timestamp
* @var string
*/
public string $createdAt = 'created_at';

/**
* Column name for updated timestamp
* @var string
*/
public string $updatedAt = 'updated_at';

/**
* Timestamp storage type: datetime|unix
* @var string
*/
public string $timestampType = 'datetime';

/**
* Determine whether the current model is new (insert) or existing (update).
* @return bool
*/
protected function isNewRecord(): bool
{
$id = $this->attributes[$this->idColumn] ?? null;

return $id === null || $id === '';
}

Comment thread
armanist marked this conversation as resolved.
/**
* Returns the current timestamp value based on model config.
* Supports: datetime|unix
* @return int|string
*/
protected function nowTimestampValue()
{
if ($this->getTimestampType() === 'unix') {
return time();
}

return date('Y-m-d H:i:s');
}

Comment thread
armanist marked this conversation as resolved.
/**
* Get timestamp type.
* @return string
*/
protected function getTimestampType(): string
{
if (defined(static::class . '::TIMESTAMP_TYPE')) {
return static::TIMESTAMP_TYPE;
}

return $this->timestampType;
}

/**
* Get "created at" column name.
* @return string
*/
protected function getCreatedAtColumn(): string
{
if (defined(static::class . '::CREATED_AT')) {
return static::CREATED_AT;
}

return $this->createdAt;
}

Comment thread
armanist marked this conversation as resolved.
/**
* Get "updated at" column name.
* @return string
*/
protected function getUpdatedAtColumn(): string
{
if (defined(static::class . '::UPDATED_AT')) {
return static::UPDATED_AT;
}

return $this->updatedAt;
}
Comment thread
armanist marked this conversation as resolved.

/**
* Touch timestamps on save
* @return void
*/
protected function touchTimestamps(): void
{
$now = $this->nowTimestampValue();

$createdAt = $this->getCreatedAtColumn();
$updatedAt = $this->getUpdatedAtColumn();

if ($this->isNewRecord()) {
if (!array_key_exists($createdAt, $this->attributes)) {
$this->attributes[$createdAt] = $now;
}
}

$this->attributes[$updatedAt] = $now;
}
}
24 changes: 18 additions & 6 deletions src/Model/Traits/SoftDeletes.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ trait SoftDeletes
*/
public function delete(): bool
{
$this->prop($this->getDeleteAtColumn(), date('Y-m-d H:i:s'));
$this->prop($this->getDeletedAtColumn(), $this->getSoftDeleteTimestampValue());
return $this->save();

}

/**
Expand All @@ -51,7 +50,7 @@ public function delete(): bool
*/
public function restore(): bool
{
$this->prop($this->getDeleteAtColumn(), null);
$this->prop($this->getDeletedAtColumn(), null);
return $this->save();
}

Expand Down Expand Up @@ -85,7 +84,7 @@ public function onlyTrashed(): self
{
$this->includeTrashed = true;

$this->getOrmInstance()->isNotNull($this->getDeleteAtColumn());
$this->getOrmInstance()->isNotNull($this->getDeletedAtColumn());

return $this;
}
Expand Down Expand Up @@ -175,15 +174,28 @@ public function first(): ?DbModel
protected function applySoftDeleteScope(): void
{
if (!$this->includeTrashed) {
$this->getOrmInstance()->isNull($this->getDeleteAtColumn());
$this->getOrmInstance()->isNull($this->getDeletedAtColumn());
}
}

/**
* Returns timestamp value for soft delete based on model timestamp config (if available).
* @return int|string
*/
protected function getSoftDeleteTimestampValue()
{
if (method_exists($this, 'nowTimestampValue')) {
return $this->nowTimestampValue();
}

return date('Y-m-d H:i:s');
}

/**
* Get the column name used for soft deletes.
* @return string
*/
protected function getDeleteAtColumn(): string
protected function getDeletedAtColumn(): string
{
if (defined(static::class . '::DELETED_AT')) {
return static::DELETED_AT;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 2.9.9
* @since 3.0.0
*/

namespace {{MODULE_NAMESPACE}}\Controllers;
Expand All @@ -30,11 +30,19 @@ class AccountController extends BaseController
* Auth service
* @var AuthService
*/
public $authService;
public AuthService $authService;

/**
* Works before an action
*/

/**
* @return void
* @throws ReflectionException
* @throws \Quantum\App\Exceptions\BaseException
* @throws \Quantum\Di\Exceptions\DiException
* @throws \Quantum\Service\Exceptions\ServiceException
*/
public function __before()
{
$this->authService = service(AuthService::class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 2.9.9
* @since 3.0.0
*/

namespace {{MODULE_NAMESPACE}}\Controllers;
Expand All @@ -31,13 +31,10 @@ class CommentController extends BaseController
/**
* @var CommentService
*/
public $commentService;
public CommentService $commentService;

/**
* @throws ReflectionException
* @throws BaseException
* @throws DiException
* @throws ServiceException
* Works before an action
*/
public function __before()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 2.9.9
* @since 3.0.0
*/

namespace {{MODULE_NAMESPACE}}\Controllers;
Expand All @@ -32,24 +32,21 @@ class PostController extends BaseController
/**
* Posts per page
*/
const POSTS_PER_PAGE = 8;
protected const POSTS_PER_PAGE = 8;

/**
* Current page
*/
const CURRENT_PAGE = 1;
protected const CURRENT_PAGE = 1;

/**
* Post service
* @var PostService
*/
public $postService;
public PostService $postService;

/**
* @throws ReflectionException
* @throws BaseException
* @throws DiException
* @throws ServiceException
* Works before an action
*/
public function __before()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
* @author Arman Ag. <arman.ag@softberg.org>
* @copyright Copyright (c) 2018 Softberg LLC (https://softberg.org)
* @link http://quantum.softberg.org/
* @since 2.9.9
* @since 3.0.0
*/

namespace {{MODULE_NAMESPACE}}\Controllers;
Expand All @@ -32,18 +32,11 @@ class PostManagementController extends BaseController
* Post service
* @var PostService
*/
public $postService;
public PostService $postService;

/**
* Works before an action
*/

/**
* @throws ReflectionException
* @throws BaseException
* @throws DiException
* @throws ServiceException
*/
public function __before()
{
$this->postService = service(PostService::class);
Expand Down Expand Up @@ -75,7 +68,6 @@ class PostManagementController extends BaseController
'title' => $request->get('title', null, true),
'content' => $request->get('content', null, true),
'image' => '',
'updated_at' => date('Y-m-d H:i:s'),
];

if ($request->hasFile('image')) {
Expand Down Expand Up @@ -109,7 +101,6 @@ class PostManagementController extends BaseController
$postData = [
'title' => $request->get('title', null, true),
'content' => $request->get('content', null, true),
'updated_at' => date('Y-m-d H:i:s'),
];

$post = $this->postService->getPost($postUuid);
Expand Down Expand Up @@ -177,7 +168,6 @@ class PostManagementController extends BaseController
'title' => $post->title,
'content' => $post->content,
'image' => '',
'updated_at' => date('Y-m-d H:i:s'),
]);

$response->json([
Expand Down
Loading