Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
27 / 27
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
EmailBlock
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
3 / 3
16
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%
14 / 14
100.00% covered (success)
100.00%
1 / 1
13
 toArray
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
2
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\Mail\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * Immutable Email Template Block Domain Model.
13 *
14 * Encapsulates an individual email structural component (heading, paragraph, CTA button,
15 * divider, callout box, etc.) designed strictly for HTML email client rendering.
16 *
17 * @package App\Modules\Mail\Domain\Model
18 */
19final readonly class EmailBlock
20{
21    /**
22     * EmailBlock constructor.
23     *
24     * @param string                $id       Unique block node identifier (e.g. blk_e1a2b3).
25     * @param string                $type     Email-specific block type (starts with email_).
26     * @param int                   $sort     Sort order index in canvas.
27     * @param array<string, mixed>  $props    Custom block properties (colors, text, links).
28     * @param array<int, self>      $children Nested child blocks (e.g. columns).
29     * @param array<string, string> $styles   Direct CSS rule overrides for email inliner.
30     */
31    public function __construct(
32        public string $id,
33        public string $type,
34        public int $sort = 0,
35        public array $props = [],
36        public array $children = [],
37        public array $styles = []
38    ) {
39    }
40
41    /**
42     * Instantiates EmailBlock from array representation.
43     *
44     * @param array<string, mixed> $data Serialized block dictionary.
45     * @return self Populated EmailBlock instance.
46     */
47    public static function fromArray(array $data): self
48    {
49        $id = isset($data['id']) && (string)$data['id'] !== ''
50            ? (string)$data['id']
51            : 'eml_' . bin2hex(random_bytes(4));
52
53        $rawType = (string)($data['type'] ?? 'email_paragraph');
54        // Ensure email_ prefix separation
55        $type = str_starts_with($rawType, 'email_') ? $rawType : 'email_' . $rawType;
56
57        $sort = isset($data['sort']) ? (int)$data['sort'] : 0;
58        $props = isset($data['props']) && is_array($data['props']) ? $data['props'] : [];
59        $styles = isset($data['styles']) && is_array($data['styles']) ? $data['styles'] : [];
60
61        $children = [];
62        if (isset($data['children']) && is_array($data['children'])) {
63            foreach ($data['children'] as $childData) {
64                if (is_array($childData)) {
65                    $children[] = self::fromArray($childData);
66                }
67            }
68        }
69
70        return new self($id, $type, $sort, $props, $children, $styles);
71    }
72
73    /**
74     * Serializes block to associative array.
75     *
76     * @return array<string, mixed> Serialized block tree.
77     */
78    public function toArray(): array
79    {
80        $serializedChildren = [];
81        foreach ($this->children as $child) {
82            $serializedChildren[] = $child->toArray();
83        }
84
85        return [
86            'id'       => $this->id,
87            'type'     => $this->type,
88            'sort'     => $this->sort,
89            'props'    => $this->props,
90            'children' => $serializedChildren,
91            'styles'   => $this->styles,
92        ];
93    }
94}