Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.24% covered (success)
95.24%
40 / 42
75.00% covered (warning)
75.00%
3 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
RelationMmMetadata
97.56% covered (success)
97.56%
40 / 41
75.00% covered (warning)
75.00%
3 / 4
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasAction
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getAllowedActions
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fromRow
97.37% covered (success)
97.37%
37 / 38
0.00% covered (danger)
0.00%
0 / 1
12
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\Core\Engine\Domain\Model;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11/**
12 * M:M Relation Metadata Value Object.
13 *
14 * Immutable value object representing a Many-to-Many entity relation configuration
15 * from a_core_relation_mm_records. Defines source and target module connections,
16 * intermediate join table mapping, sort order, and view scope.
17 *
18 * @package App\Core\Engine\Domain\Model
19 */
20final readonly class RelationMmMetadata
21{
22    /**
23     * RelationMmMetadata constructor.
24     *
25     * @param int                  $id                Relation primary key.
26     * @param string               $name              Machine name identifier (e.g. 'companies_contacts').
27     * @param string               $label             Display label (e.g. 'Kontakty').
28     * @param array<int, int>      $sourceModuleIds   Source module IDs list.
29     * @param array<int, int>      $targetModuleIds   Target module IDs list.
30     * @param string               $intermediateTable Association table name.
31     * @param string               $relationType      Relation type (default 'M:M').
32     * @param string               $viewScope         View scope ('detail', 'all').
33     * @param int                  $recordLimit       Default display limit per page.
34     * @param bool                 $isActive          Whether this relation is active.
35     * @param int                  $sortOrder         Ordering position in tabs.
36     * @param array<int, string>   $visibleFields     Configured visible field keys list.
37     * @param string|null          $sourceModuleName  Optional joined source module name.
38     * @param string|null          $targetModuleName  Optional joined target module machine name.
39     * @param string|null          $targetModuleRoute Optional joined target module route URL.
40     * @param string|null          $targetModuleIcon  Optional joined target module icon CSS class.
41     * @param string|null          $targetModuleLabel Optional joined target module human label.
42     */
43    public function __construct(
44        public readonly int     $id,
45        public readonly string  $name,
46        public readonly string  $label,
47        public readonly array   $sourceModuleIds,
48        public readonly array   $targetModuleIds,
49        public readonly string  $intermediateTable,
50        public readonly string  $relationType = 'M:M',
51        public readonly string  $viewScope = 'detail',
52        public readonly int     $recordLimit = 20,
53        public readonly bool    $isActive = true,
54        public readonly int     $sortOrder = 10,
55        public readonly array   $visibleFields = [],
56        public readonly ?string $sourceModuleName = null,
57        public readonly ?string $targetModuleName = null,
58        public readonly ?string $targetModuleRoute = null,
59        public readonly ?string $targetModuleIcon = null,
60        public readonly ?string $targetModuleLabel = null,
61        public readonly array   $allowedActions = ['create', 'select', 'refresh'],
62    ) {
63    }
64
65    /**
66     * Checks if a specific action is permitted in this relation.
67     *
68     * @param string $action Action key to test ('create', 'select', 'refresh', 'pdf').
69     * @return bool True if action is allowed.
70     */
71    public function hasAction(string $action): bool
72    {
73        return in_array($action, $this->allowedActions, true);
74    }
75
76    /**
77     * Returns the array of allowed action identifiers.
78     *
79     * @return array<int, string>
80     */
81    public function getAllowedActions(): array
82    {
83        return $this->allowedActions;
84    }
85
86    /**
87     * Creates a RelationMmMetadata instance from a raw database row array.
88     *
89     * @param array<string, mixed> $row Raw database row from a_core_relation_mm_records.
90     * @return self Hydrated RelationMmMetadata value object.
91     */
92    public static function fromRow(array $row): self
93    {
94        $rawSource = $row['source_module_ids'] ?? '[]';
95        $sourceIds = is_string($rawSource)
96            ? (array) (json_decode($rawSource, true) ?? [])
97            : (array) $rawSource;
98
99        $rawTarget = $row['target_module_ids'] ?? '[]';
100        $targetIds = is_string($rawTarget)
101            ? (array) (json_decode($rawTarget, true) ?? [])
102            : (array) $rawTarget;
103
104        $rawVisible = $row['visible_fields'] ?? '[]';
105        $visibleFields = is_string($rawVisible)
106            ? (array) (json_decode($rawVisible, true) ?? [])
107            : (array) $rawVisible;
108
109        $rawAllowed = $row['allowed_actions'] ?? null;
110        $allowedActions = ['create', 'select', 'refresh'];
111        if ($rawAllowed !== null) {
112            $decoded = is_string($rawAllowed) ? json_decode($rawAllowed, true) : $rawAllowed;
113            if (is_array($decoded)) {
114                $allowedActions = array_values(array_filter(array_map('strval', $decoded)));
115            }
116        }
117
118        return new self(
119            id:                (int)    $row['id'],
120            name:              (string) $row['name'],
121            label:             (string) $row['label'],
122            sourceModuleIds:   array_map('intval', $sourceIds),
123            targetModuleIds:   array_map('intval', $targetIds),
124            intermediateTable: (string) ($row['intermediate_table'] ?? ''),
125            relationType:      (string) ($row['relation_type']      ?? 'M:M'),
126            viewScope:         (string) ($row['view_scope']         ?? 'detail'),
127            recordLimit:       (int)    ($row['record_limit']       ?? 20),
128            isActive:          (bool)   ($row['is_active']          ?? true),
129            sortOrder:         (int)    ($row['sort_order']         ?? 10),
130            visibleFields:     array_values(array_filter(array_map('strval', $visibleFields))),
131            sourceModuleName:  isset($row['source_module_name']) ? (string) $row['source_module_name'] : null,
132            targetModuleName:  isset($row['target_module_name']) ? (string) $row['target_module_name'] : null,
133            targetModuleRoute: isset($row['target_module_route']) ? (string) $row['target_module_route'] : null,
134            targetModuleIcon:  isset($row['target_module_icon']) ? (string) $row['target_module_icon'] : null,
135            targetModuleLabel: isset($row['target_module_label']) ? (string) $row['target_module_label'] : null,
136            allowedActions:    $allowedActions,
137        );
138    }
139}