feat(Docker): Upgrade to PHP 8.5.0RC3 with native ext-uri support

BREAKING CHANGE: Requires PHP 8.5.0RC3

Changes:
- Update Docker base image from php:8.4-fpm to php:8.5.0RC3-fpm
- Enable ext-uri for native WHATWG URL parsing support
- Update composer.json PHP requirement from ^8.4 to ^8.5
- Add ext-uri as required extension in composer.json
- Move URL classes from Url.php85/ to Url/ directory (now compatible)
- Remove temporary PHP 8.4 compatibility workarounds

Benefits:
- Native URL parsing with Uri\WhatWg\Url class
- Better performance for URL operations
- Future-proof with latest PHP features
- Eliminates PHP version compatibility issues
This commit is contained in:
2025-10-27 09:31:28 +01:00
parent 799f74f00a
commit c8b47e647d
81 changed files with 6988 additions and 601 deletions

View File

@@ -6,7 +6,8 @@ namespace App\Framework\Notification\Storage;
use App\Framework\Core\ValueObjects\Timestamp;
use App\Framework\Database\ConnectionInterface;
use App\Framework\Database\SqlQuery;
use App\Framework\Database\ValueObjects\SqlQuery;
use App\Framework\DI\Attributes\DefaultImplementation;
use App\Framework\Notification\Notification;
use App\Framework\Notification\ValueObjects\NotificationChannel;
use App\Framework\Notification\ValueObjects\NotificationId;
@@ -17,17 +18,16 @@ use App\Framework\Notification\ValueObjects\NotificationType;
/**
* Database implementation of NotificationRepository
*/
#[DefaultImplementation]
final readonly class DatabaseNotificationRepository implements NotificationRepository
{
public function __construct(
private ConnectionInterface $connection
) {
}
) {}
public function save(Notification $notification): void
{
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(<<<'SQL'
INSERT INTO notifications (
id, recipient_id, type, title, body, data,
channels, priority, status, created_at, sent_at,
@@ -38,7 +38,7 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
sent_at = EXCLUDED.sent_at,
read_at = EXCLUDED.read_at
SQL,
params: [
[
$notification->id->toString(),
$notification->recipientId,
$notification->type->toString(),
@@ -61,9 +61,9 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function findById(NotificationId $id): ?Notification
{
$query = new SqlQuery(
sql: 'SELECT * FROM notifications WHERE id = ?',
params: [$id->toString()]
$query = SqlQuery::create(
'SELECT * FROM notifications WHERE id = ?',
[$id->toString()]
);
$row = $this->connection->queryOne($query);
@@ -73,14 +73,14 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function findByUser(string $userId, int $limit = 20, int $offset = 0): array
{
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(
<<<'SQL'
SELECT * FROM notifications
WHERE recipient_id = ?
ORDER BY created_at DESC
LIMIT ? OFFSET ?
SQL,
params: [$userId, $limit, $offset]
[$userId, $limit, $offset]
);
$rows = $this->connection->query($query)->fetchAll();
@@ -90,15 +90,15 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function findUnreadByUser(string $userId, int $limit = 20): array
{
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(
<<<'SQL'
SELECT * FROM notifications
WHERE recipient_id = ?
AND status != ?
ORDER BY created_at DESC
LIMIT ?
SQL,
params: [$userId, NotificationStatus::READ->value, $limit]
[$userId, NotificationStatus::READ->value, $limit]
);
$rows = $this->connection->query($query)->fetchAll();
@@ -108,13 +108,13 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function countUnreadByUser(string $userId): int
{
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(
<<<'SQL'
SELECT COUNT(*) as count FROM notifications
WHERE recipient_id = ?
AND status != ?
SQL,
params: [$userId, NotificationStatus::READ->value]
[$userId, NotificationStatus::READ->value]
);
return (int) $this->connection->queryScalar($query);
@@ -122,15 +122,15 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function markAsRead(NotificationId $id): bool
{
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(
<<<'SQL'
UPDATE notifications
SET status = ?, read_at = ?
WHERE id = ?
SQL,
params: [
[
NotificationStatus::READ->value,
(new Timestamp())->format('Y-m-d H:i:s'),
Timestamp::now()->format('Y-m-d H:i:s'),
$id->toString(),
]
);
@@ -140,16 +140,16 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function markAllAsReadForUser(string $userId): int
{
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(
<<<'SQL'
UPDATE notifications
SET status = ?, read_at = ?
WHERE recipient_id = ?
AND status != ?
SQL,
params: [
[
NotificationStatus::READ->value,
(new Timestamp())->format('Y-m-d H:i:s'),
Timestamp::now()->format('Y-m-d H:i:s'),
$userId,
NotificationStatus::READ->value,
]
@@ -160,9 +160,9 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
public function delete(NotificationId $id): bool
{
$query = new SqlQuery(
sql: 'DELETE FROM notifications WHERE id = ?',
params: [$id->toString()]
$query = SqlQuery::create(
'DELETE FROM notifications WHERE id = ?',
[$id->toString()]
);
return $this->connection->execute($query) > 0;
@@ -172,13 +172,13 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
{
$cutoffDate = (new Timestamp())->modify("-{$daysOld} days");
$query = new SqlQuery(
sql: <<<'SQL'
$query = SqlQuery::create(
<<<'SQL'
DELETE FROM notifications
WHERE status = ?
AND created_at < ?
SQL,
params: [
[
$status->value,
$cutoffDate->format('Y-m-d H:i:s'),
]
@@ -195,19 +195,19 @@ final readonly class DatabaseNotificationRepository implements NotificationRepos
);
return new Notification(
id: NotificationId::fromString($row['id']),
id : NotificationId::fromString($row['id']),
recipientId: $row['recipient_id'],
type: NotificationType::fromString($row['type']),
title: $row['title'],
body: $row['body'],
data: json_decode($row['data'], true) ?? [],
channels: $channels,
priority: NotificationPriority::from($row['priority']),
status: NotificationStatus::from($row['status']),
createdAt: Timestamp::fromString($row['created_at']),
sentAt: $row['sent_at'] ? Timestamp::fromString($row['sent_at']) : null,
readAt: $row['read_at'] ? Timestamp::fromString($row['read_at']) : null,
actionUrl: $row['action_url'],
type : NotificationType::fromString($row['type']),
title : $row['title'],
body : $row['body'],
createdAt : Timestamp::fromTimestamp((int) strtotime($row['created_at'])),
data : json_decode($row['data'], true) ?? [],
channels : $channels,
priority : NotificationPriority::from($row['priority']),
status : NotificationStatus::from($row['status']),
sentAt : $row['sent_at'] ? Timestamp::fromTimestamp((int) strtotime($row['sent_at'])) : null,
readAt : $row['read_at'] ? Timestamp::fromTimestamp((int) strtotime($row['read_at'])) : null,
actionUrl : $row['action_url'],
actionLabel: $row['action_label']
);
}

View File

@@ -45,10 +45,10 @@ final readonly class TemplateRenderer
// Create base notification
$notification = Notification::create(
recipientId: $recipientId,
type: $type,
title: $title,
body: $body,
$recipientId,
$type,
$title,
$body,
...$channels
)->withPriority($template->defaultPriority);

View File

@@ -7,7 +7,7 @@ namespace App\Framework\Notification\ValueObjects;
/**
* Type/Category of notification for user preferences and filtering
*/
final readonly class NotificationType
final readonly class NotificationType implements NotificationTypeInterface
{
private function __construct(
private string $value
@@ -57,4 +57,14 @@ final readonly class NotificationType
{
return $this->value === $other->value;
}
public function getDisplayName(): string
{
return $this->value;
}
public function isCritical(): bool
{
return false;
}
}