Skip to content

Commit 4f4dbf8

Browse files
authored
Merge pull request #539 from WoltLab/6.2-dbo-collection
Document database object collections
2 parents 9c9551e + 2d4dad1 commit 4f4dbf8

File tree

1 file changed

+95
-0
lines changed

1 file changed

+95
-0
lines changed

docs/php/database-objects.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,101 @@ Of course, you do not have to set the property after creating the list object, y
169169
) }}
170170

171171

172+
## DatabaseObjectCollection
173+
174+
A Database Object Collection is a container for a group of `DatabaseObject` instances that optimizes how additional runtime data is loaded and shared between objects.
175+
176+
### Motivation
177+
178+
Some database objects require extra runtime data, for example, a `UserProfile` or file attachments.
179+
Loading this data on demand per object is inefficient, as it requires repeated queries for each object.
180+
181+
Earlier approaches like the `Viewable*` classes tried to address this by preloading extra data:
182+
183+
- ✅ Efficient in some cases, especially when JOINs are used.
184+
- ❌ Can be wasteful if not all of the extra data is needed.
185+
- ❌ JOIN-based solutions lack reliable type hints, sometimes resulting in missing or inconsistent data.
186+
- ❌ Introduces additional wrapper classes, complicating inheritance and interface checks.
187+
188+
### Concept
189+
190+
A Database Object Collection solves these issues by grouping objects and handling extra data more intelligently:
191+
192+
- Objects are fetched together as a collection.
193+
- Extra data is not loaded upfront, but on first access.
194+
- When extra data is requested for one object, it is fetched for all objects in the collection at once.
195+
- This balances efficiency with flexibility — data is only loaded when needed, but subsequent lookups are inexpensive.
196+
197+
This leads to the following benefits:
198+
199+
- Lazy loading: Extra data is only fetched when required.
200+
- Batch efficiency: Once requested, data for all objects in the collection is retrieved together.
201+
- Type safety: Avoids unreliable JOIN-based type ambiguities.
202+
- Cleaner design: Eliminates the need for complicated wrapper classes like Viewable*.
203+
204+
### Usage Example
205+
206+
A database object that is to be part of a collection should extend `CollectionDatabaseObject`.
207+
`CollectionDatabaseObject` takes care of instantiating the collection.
208+
Database objects that have been read together via a database object list are automatically combined into a shared collection.
209+
210+
```php
211+
class FooObject extends CollectionDatabaseObject {
212+
public function getUserProfile(): UserProfile
213+
{
214+
return $this->getCollection()->getUserProfile($this);
215+
}
216+
}
217+
```
218+
219+
When instantiating the collection, `CollectionDatabaseObject` attempts to find a class with the same name but with the added suffix `Collection`.
220+
However, by overwriting the `getCollectionClassName()` method, you can also use a custom class name.
221+
222+
```php
223+
class FooObjectCollection extends DatabaseObjectCollection {
224+
private bool $userProfilesLoaded = false;
225+
226+
public function getUserProfile(FooObject $object): UserProfile
227+
{
228+
$this->loadUserProfiles();
229+
230+
if ($object->userID) {
231+
return UserProfileRuntimeCache::getInstance()->getObject($object->userID);
232+
} else {
233+
return UserProfile::getGuestUserProfile($object->username);
234+
}
235+
}
236+
237+
private function loadUserProfiles(): void
238+
{
239+
if ($this->userProfilesLoaded) {
240+
return;
241+
}
242+
243+
$this->userProfilesLoaded = true;
244+
245+
$userIDs = [];
246+
foreach ($this->getObjects() as $object) {
247+
if ($object->userID) {
248+
$userIDs[] = $object->userID;
249+
}
250+
}
251+
252+
if ($userIDs !== []) {
253+
UserProfileRuntimeCache::getInstance()->cacheObjectIDs($userIDs);
254+
}
255+
}
256+
}
257+
```
258+
259+
### Traits
260+
261+
Currently, two traits are available to handle typical standard use cases with a collection:
262+
263+
* `TCollectionUserProfiles`: Handles the loading of user profiles that belong to the database objects.
264+
* `TCollectionEmbeddedObjects`: Handles the loading of embedded objects that belong to the database objects.
265+
266+
172267
## AbstractDatabaseObjectAction
173268

174269
Row creation and manipulation can be performed using the aforementioned `DatabaseObjectEditor` class, but this approach has two major issues:

0 commit comments

Comments
 (0)