3030 * Model class that represents single record in database.
3131 *
3232 * @property int|string $id
33+ * @property array<string,array<string,mixed>> $__properties
34+ * @property array<string,mixed> $__meta
3335 */
3436class Model implements \Stringable, \IteratorAggregate, \ArrayAccess
3537{
@@ -40,39 +42,82 @@ class Model implements \Stringable, \IteratorAggregate, \ArrayAccess
4042 use Setter;
4143
4244 /**
43- * Store all properties of model.
44- *
45+ * Relation type constants
46+ */
47+ private const RELATION_ONE_TO_MANY = 'otm ' ;
48+ private const RELATION_ONE_TO_ONE = 'oto ' ;
49+ private const RELATION_MANY_TO_MANY = 'mtm ' ;
50+
51+ /**
52+ * Property type constants
53+ */
54+ private const TYPE_JSON = 'json_document ' ;
55+ private const TYPE_TEXT = 'text ' ;
56+ private const TYPE_FLOAT = 'float ' ;
57+
58+ /**
59+ * Relation keywords
60+ */
61+ private const KEYWORD_OWN = 'own ' ;
62+ private const KEYWORD_SHARED = 'shared ' ;
63+ private const KEYWORD_LIST = 'list ' ;
64+
65+ /**
66+ * Valid relation types
67+ */
68+ private const RELATION_TYPES = [
69+ self ::RELATION_ONE_TO_MANY ,
70+ self ::RELATION_ONE_TO_ONE ,
71+ self ::RELATION_MANY_TO_MANY
72+ ];
73+
74+ /**
4575 * @var array<string,array<string,mixed>>
4676 */
4777 private array $ __properties = [];
78+
4879 /**
49- * Store the metadata of model.
50- *
5180 * @var array<string,mixed>
5281 */
5382 private array $ __meta = [];
5483
5584 /**
56- * Create a new model.
85+ * Cache for relation table names
86+ * @var array<string,string>
5787 */
88+ private array $ relationTableCache = [];
89+
5890 public function __construct (
59- /**
60- * Store the table name of model.
61- */
6291 private string $ table ,
6392 private Connection $ connection ,
6493 private RecordManager $ recordManager ,
6594 private TableManager $ tableManager ,
6695 private WriteManager $ writeManager ,
6796 ) {
68- $ this ->__properties ['all ' ] = [];
69- $ this ->__properties ['self ' ] = [];
70- $ this ->__meta ['is_loaded ' ] = false ;
71- $ this ->__meta ['id_error ' ] = false ;
72- $ this ->__meta ['foreign_models ' ]['otm ' ] = null ;
73- $ this ->__meta ['foreign_models ' ]['oto ' ] = null ;
74- $ this ->__meta ['foreign_models ' ]['mtm ' ] = null ;
75- $ this ->__meta ['id ' ] = 0 ;
97+ $ this ->initializeProperties ();
98+ }
99+
100+ /**
101+ * Initialize model properties and metadata
102+ */
103+ private function initializeProperties (): void
104+ {
105+ $ this ->__properties = [
106+ 'all ' => [],
107+ 'self ' => [],
108+ 'type ' => []
109+ ];
110+
111+ $ this ->__meta = [
112+ 'is_loaded ' => false ,
113+ 'id_error ' => false ,
114+ 'foreign_models ' => [
115+ self ::RELATION_ONE_TO_MANY => null ,
116+ self ::RELATION_ONE_TO_ONE => null ,
117+ self ::RELATION_MANY_TO_MANY => null
118+ ],
119+ 'id ' => 0
120+ ];
76121 }
77122
78123 /**
@@ -115,11 +160,11 @@ public function set(string $key, mixed $val): void
115160
116161 private function handleRelationalKey (string $ key , array $ parts ): mixed
117162 {
118- if (' own ' === strtolower ((string ) $ parts [0 ])) {
163+ if (self :: KEYWORD_OWN === strtolower ((string ) $ parts [0 ])) {
119164 return $ this ->handleOwnRelation ($ key , $ parts );
120165 }
121166
122- if (' shared ' === strtolower ((string ) $ parts [0 ])) {
167+ if (self :: KEYWORD_SHARED === strtolower ((string ) $ parts [0 ])) {
123168 return $ this ->handleSharedRelation ($ key , $ parts );
124169 }
125170
@@ -128,19 +173,19 @@ private function handleRelationalKey(string $key, array $parts): mixed
128173
129174 private function handleOwnRelation (string $ key , array $ parts ): mixed
130175 {
131- if (' list ' !== strtolower ((string ) $ parts [2 ])) {
176+ if (self :: KEYWORD_LIST !== strtolower ((string ) $ parts [2 ])) {
132177 return null ;
133178 }
134179
135- $ qb = $ this ->recordManager ->find (strtolower ((string ) $ parts [1 ]));
136- $ qb ->where ($ this ->getName () . '_id = :id ' )
180+ $ db = $ this ->recordManager ->find (strtolower ((string ) $ parts [1 ]));
181+ $ db ->where ($ this ->getName () . '_id = :id ' )
137182 ->setParameter ('id ' , $ this ->__meta ['id ' ],
138183 $ this ->determineIdType ($ this ->__meta ['id ' ])
139184 );
140-
141- $ result = $ qb ->get ();
142185
186+ $ result = $ db ->get ();
143187 $ this ->set ($ key , $ result );
188+
144189 return $ result ;
145190 }
146191
@@ -180,9 +225,15 @@ private function handleSharedRelation(string $key, array $parts): mixed
180225
181226 private function getRelationTable (string $ targetTable ): string
182227 {
183- return $ this ->tableManager ->tableExists ($ this ->table . '_ ' . $ targetTable )
184- ? $ this ->table . '_ ' . $ targetTable
185- : $ targetTable . '_ ' . $ this ->table ;
228+ $ cacheKey = $ this ->table . '_ ' . $ targetTable ;
229+
230+ if (!isset ($ this ->relationTableCache [$ cacheKey ])) {
231+ $ this ->relationTableCache [$ cacheKey ] = $ this ->tableManager ->tableExists ($ cacheKey )
232+ ? $ cacheKey
233+ : $ targetTable . '_ ' . $ this ->table ;
234+ }
235+
236+ return $ this ->relationTableCache [$ cacheKey ];
186237 }
187238
188239 private function extractRelationIds (Collection $ relations , string $ targetTable ): array
@@ -350,33 +401,18 @@ public function equals(self $other): bool
350401 }
351402
352403 /**
353- * Check if model has any relations.
354- */
355- public function hasForeign (string $ type ): bool
356- {
357- return !is_null ($ this ->__meta ['foreign_models ' ][$ type ]);
358- }
359-
360- /**
361- * Get the type of value.
404+ * Get the type of value
362405 */
363406 private function getDataType (mixed $ value ): string
364407 {
365408 $ type = gettype ($ value );
366409
367- if ('array ' === $ type || 'object ' === $ type ) {
368- return 'json_document ' ;
369- }
370-
371- if ('string ' === $ type ) {
372- return 'text ' ;
373- }
374-
375- if ('double ' === $ type ) {
376- return 'float ' ;
377- }
378-
379- return $ type ;
410+ return match ($ type ) {
411+ 'array ' , 'object ' => self ::TYPE_JSON ,
412+ 'string ' => self ::TYPE_TEXT ,
413+ 'double ' => self ::TYPE_FLOAT ,
414+ default => $ type
415+ };
380416 }
381417
382418 /**
@@ -433,23 +469,21 @@ private function handleComplexRelation(string $key, mixed $val): bool
433469 $ parts = \Safe \preg_split ('/(?=[A-Z])/ ' , $ key , -1 , PREG_SPLIT_NO_EMPTY );
434470 $ type = strtolower ((string ) $ parts [0 ]);
435471
436- if (' own ' === $ type ) {
437- $ this ->__meta ['foreign_models ' ][' otm ' ] = $ this ->createCollection (
438- $ this ->__meta ['foreign_models ' ][' otm ' ],
472+ if (self :: KEYWORD_OWN === $ type ) {
473+ $ this ->__meta ['foreign_models ' ][self :: RELATION_ONE_TO_MANY ] = $ this ->createCollection (
474+ $ this ->__meta ['foreign_models ' ][self :: RELATION_ONE_TO_MANY ],
439475 $ val
440476 );
441477 $ this ->__properties ['all ' ][$ key ] = $ val ;
442-
443478 return true ;
444479 }
445480
446- if (' shared ' === $ type ) {
447- $ this ->__meta ['foreign_models ' ][' mtm ' ] = $ this ->createCollection (
448- $ this ->__meta ['foreign_models ' ][' mtm ' ],
481+ if (self :: KEYWORD_SHARED === $ type ) {
482+ $ this ->__meta ['foreign_models ' ][self :: RELATION_MANY_TO_MANY ] = $ this ->createCollection (
483+ $ this ->__meta ['foreign_models ' ][self :: RELATION_MANY_TO_MANY ],
449484 $ val
450485 );
451486 $ this ->__properties ['all ' ][$ key ] = $ val ;
452-
453487 return true ;
454488 }
455489
@@ -491,4 +525,20 @@ private function determineIdsType(array $ids): ArrayParameterType
491525 };
492526 }
493527
528+ /**
529+ * Validates if the given relation type is valid
530+ * @throws Exception\InvalidRelationTypeException
531+ */
532+ private function validateRelationType (string $ type ): void
533+ {
534+ if (!in_array ($ type , self ::RELATION_TYPES , true )) {
535+ throw new Exception \InvalidRelationTypeException ($ type );
536+ }
537+ }
538+
539+ public function hasForeign (string $ type ): bool
540+ {
541+ $ this ->validateRelationType ($ type );
542+ return !is_null ($ this ->__meta ['foreign_models ' ][$ type ]);
543+ }
494544}
0 commit comments