Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
9 / 9
CRAP
100.00% covered (success)
100.00%
1 / 1
CommentAttachment
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
9 / 9
19
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromArray
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 fileIconClass
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formattedSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isImage
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 formatSize
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getIconClass
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
9
 toArray
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
1
 jsonSerialize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Modules\Comments\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use JsonSerializable;
12
13/**
14 * Domain entity representing a comment attachment.
15 *
16 * @package App\Modules\Comments\Domain\Model
17 */
18final readonly class CommentAttachment implements JsonSerializable
19{
20    private const array IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'];
21
22    public string $downloadToken;
23
24    /**
25     * CommentAttachment constructor.
26     */
27    public function __construct(
28        public int $id,
29        public int $commentId,
30        public string $fileName,
31        public string $filePath,
32        public int $fileSize,
33        public string $fileExtension,
34        public string $mimeType,
35        public string $token,
36        public int $createdBy,
37        public string $createdAt,
38        ?string $downloadToken = null
39    ) {
40        $this->downloadToken = $downloadToken ?? $this->token;
41    }
42
43    /**
44     * Static factory creating entity from associative array.
45     *
46     * @param array<string, mixed> $data
47     */
48    public static function fromArray(array $data): self
49    {
50        $token = (string) ($data['token'] ?? $data['download_token'] ?? '');
51        return new self(
52            id:            (int) ($data['id'] ?? 0),
53            commentId:     (int) ($data['comment_id'] ?? 0),
54            fileName:      (string) ($data['file_name'] ?? ''),
55            filePath:      (string) ($data['file_path'] ?? ''),
56            fileSize:      (int) ($data['file_size'] ?? 0),
57            fileExtension: (string) ($data['file_extension']
58                ?? pathinfo((string) ($data['file_name'] ?? ''), PATHINFO_EXTENSION)),
59            mimeType:      (string) ($data['mime_type'] ?? 'application/octet-stream'),
60            token:         $token,
61            createdBy:     (int) ($data['created_by'] ?? 0),
62            createdAt:     (string) ($data['created_at'] ?? date('Y-m-d H:i:s')),
63            downloadToken: $token
64        );
65    }
66
67    /**
68     * Alias for getIconClass for Twig template convenience.
69     */
70    public function fileIconClass(): string
71    {
72        return $this->getIconClass();
73    }
74
75    /**
76     * Alias for formatSize for Twig template convenience.
77     */
78    public function formattedSize(): string
79    {
80        return $this->formatSize();
81    }
82
83    /**
84     * Checks if this attachment is an image supported for thumbnail preview.
85     */
86    public function isImage(): bool
87    {
88        return in_array(strtolower($this->fileExtension), self::IMAGE_EXTENSIONS, true);
89    }
90
91    /**
92     * Formats bytes into human-readable size string.
93     */
94    public function formatSize(): string
95    {
96        if ($this->fileSize >= 1048576) {
97            return round($this->fileSize / 1048576, 1) . ' MB';
98        }
99        if ($this->fileSize >= 1024) {
100            return round($this->fileSize / 1024, 1) . ' KB';
101        }
102
103        return $this->fileSize . ' B';
104    }
105
106    /**
107     * Resolves Bootstrap icon CSS class based on file extension.
108     */
109    public function getIconClass(): string
110    {
111        $ext = strtolower($this->fileExtension);
112
113        return match ($ext) {
114            'pdf' => 'bi bi-file-earmark-pdf text-danger',
115            'doc', 'docx', 'odt', 'rtf' => 'bi bi-file-earmark-word text-primary',
116            'xls', 'xlsx', 'ods', 'csv' => 'bi bi-file-earmark-excel text-success',
117            'ppt', 'pptx', 'odp' => 'bi bi-file-earmark-ppt text-warning',
118            'zip', 'rar', '7z', 'tar', 'gz' => 'bi bi-file-earmark-zip text-secondary',
119            'jpg', 'jpeg', 'png', 'gif', 'webp', 'svg' => 'bi bi-file-earmark-image text-info',
120            'txt', 'json', 'xml', 'md' => 'bi bi-file-earmark-text text-body-secondary',
121            default => 'bi bi-file-earmark text-secondary',
122        };
123    }
124
125    /**
126     * Exports entity state as associative array.
127     *
128     * @return array<string, mixed>
129     */
130    public function toArray(): array
131    {
132        return [
133            'id'             => $this->id,
134            'comment_id'     => $this->commentId,
135            'file_name'      => $this->fileName,
136            'file_path'      => $this->filePath,
137            'file_size'      => $this->fileSize,
138            'file_size_fmt'  => $this->formatSize(),
139            'file_extension' => $this->fileExtension,
140            'mime_type'      => $this->mimeType,
141            'token'          => $this->token,
142            'is_image'       => $this->isImage(),
143            'icon_class'     => $this->getIconClass(),
144            'created_by'     => $this->createdBy,
145            'created_at'     => $this->createdAt,
146        ];
147    }
148
149    /**
150     * Serializes entity to JSON representation.
151     *
152     * @return array<string, mixed>
153     */
154    public function jsonSerialize(): array
155    {
156        return $this->toArray();
157    }
158}