1: <?php
2:
3: declare(strict_types=1);
4:
5: namespace Atk4\Data\Model;
6:
7: use Atk4\Data\Exception;
8: use Atk4\Data\Model;
9: use Atk4\Data\Reference;
10:
11: /**
12: * Provides native Model methods for manipulating model references.
13: */
14: trait ReferencesTrait
15: {
16: /** @var array<mixed> The seed used by addReference() method. */
17: protected $_defaultSeedAddReference = [Reference::class];
18:
19: /** @var array<mixed> The seed used by hasOne() method. */
20: protected $_defaultSeedHasOne = [Reference\HasOne::class];
21:
22: /** @var array<mixed> The seed used by hasMany() method. */
23: protected $_defaultSeedHasMany = [Reference\HasMany::class];
24:
25: /** @var array<mixed> The seed used by containsOne() method. */
26: protected $_defaultSeedContainsOne = [Reference\ContainsOne::class];
27:
28: /** @var array<mixed> The seed used by containsMany() method. */
29: protected $_defaultSeedContainsMany = [Reference\ContainsMany::class];
30:
31: /**
32: * @param array<mixed> $seed
33: * @param array<string, mixed> $defaults
34: */
35: protected function _addReference(array $seed, string $link, array $defaults = []): Reference
36: {
37: $this->assertIsModel();
38:
39: $defaults[0] = $link;
40:
41: $reference = Reference::fromSeed($seed, $defaults);
42:
43: $name = $reference->getDesiredName();
44: if ($this->hasElement($name)) {
45: throw (new Exception('Reference with such name already exists'))
46: ->addMoreInfo('name', $name)
47: ->addMoreInfo('link', $link);
48: }
49:
50: $this->add($reference);
51:
52: return $reference;
53: }
54:
55: /**
56: * Add generic relation. Provide your own call-back that will return the model.
57: *
58: * @param array<string, mixed> $defaults
59: */
60: public function addReference(string $link, array $defaults): Reference
61: {
62: return $this->_addReference($this->_defaultSeedAddReference, $link, $defaults);
63: }
64:
65: /**
66: * Add hasOne reference.
67: *
68: * @param array<string, mixed> $defaults
69: *
70: * @return Reference\HasOne|Reference\HasOneSql
71: */
72: public function hasOne(string $link, array $defaults = []) // : Reference
73: {
74: // @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/9022
75: return $this->_addReference($this->_defaultSeedHasOne, $link, $defaults); // @phpstan-ignore-line
76: }
77:
78: /**
79: * Add hasMany reference.
80: *
81: * @param array<string, mixed> $defaults
82: *
83: * @return Reference\HasMany
84: */
85: public function hasMany(string $link, array $defaults = []) // : Reference
86: {
87: // @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/9022
88: return $this->_addReference($this->_defaultSeedHasMany, $link, $defaults); // @phpstan-ignore-line
89: }
90:
91: /**
92: * Add containsOne reference.
93: *
94: * @param array<string, mixed> $defaults
95: *
96: * @return Reference\ContainsOne
97: */
98: public function containsOne(string $link, array $defaults = []) // : Reference
99: {
100: // @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/9022
101: return $this->_addReference($this->_defaultSeedContainsOne, $link, $defaults); // @phpstan-ignore-line
102: }
103:
104: /**
105: * Add containsMany reference.
106: *
107: * @param array<string, mixed> $defaults
108: *
109: * @return Reference\ContainsMany
110: */
111: public function containsMany(string $link, array $defaults = []) // : Reference
112: {
113: // @phpstan-ignore-next-line https://github.com/phpstan/phpstan/issues/9022
114: return $this->_addReference($this->_defaultSeedContainsMany, $link, $defaults); // @phpstan-ignore-line
115: }
116:
117: public function hasReference(string $link): bool
118: {
119: return $this->getModel(true)->hasElement('#ref-' . $link);
120: }
121:
122: public function getReference(string $link): Reference
123: {
124: $this->assertIsModel();
125:
126: return $this->getElement('#ref-' . $link);
127: }
128:
129: /**
130: * @return array<string, Reference>
131: */
132: public function getReferences(): array
133: {
134: $this->assertIsModel();
135:
136: $res = [];
137: foreach ($this->elements as $k => $v) {
138: if (str_starts_with($k, '#ref-')) {
139: $link = substr($k, strlen('#ref-'));
140: $res[$link] = $this->getReference($link);
141: } elseif ($v instanceof Reference) {
142: throw new \Error('Unexpected Reference index');
143: }
144: }
145:
146: return $res;
147: }
148:
149: /**
150: * Traverse to related model.
151: *
152: * @param array<string, mixed> $defaults
153: */
154: public function ref(string $link, array $defaults = []): Model
155: {
156: return $this->getModel(true)->getReference($link)->ref($this, $defaults);
157: }
158:
159: /**
160: * Return related model.
161: *
162: * @param array<string, mixed> $defaults
163: */
164: public function refModel(string $link, array $defaults = []): Model
165: {
166: return $this->getModel(true)->getReference($link)->refModel($this, $defaults);
167: }
168:
169: /**
170: * Returns model that can be used for generating sub-query actions.
171: *
172: * @param array<string, mixed> $defaults
173: */
174: public function refLink(string $link, array $defaults = []): Model
175: {
176: return $this->getModel(true)->getReference($link)->refLink($this, $defaults);
177: }
178: }
179: