|
| 1 | +.. _collection_preload: |
| 2 | + |
| 3 | +================================= |
| 4 | +WebDAV collection preload events |
| 5 | +================================= |
| 6 | + |
| 7 | +Overview |
| 8 | +-------- |
| 9 | + |
| 10 | +During a WebDAV PROPFIND on a SabreDAV Collection with Depth > 0, |
| 11 | +Nextcloud emits a preload event so DAV plugins can fetch data for the collection's |
| 12 | +children in one go. The goal is to avoid N+1 database queries by |
| 13 | +preloading what your property handlers will need before per-node property |
| 14 | +handling starts. In practice this means your plugin can fill a cache up |
| 15 | +front and then read from it in the usual ``propFind`` handlers for a faster |
| 16 | +and more efficient response. |
| 17 | + |
| 18 | +When is the event emitted |
| 19 | +------------------------- |
| 20 | + |
| 21 | +The event is emitted during PROPFIND requests, before the ``propFind`` event |
| 22 | +is emitted, against nodes implementing ``Sabre\DAV\ICollection`` with Depth > 0. |
| 23 | + |
| 24 | +The event may be emitted multiple times for a single request path; plugins should |
| 25 | +check their cache to avoid duplicate work. |
| 26 | + |
| 27 | +Subscribing to the event in a plugin |
| 28 | +------------------------------------ |
| 29 | + |
| 30 | +Register a listener in your implementation of Sabre's ``ServerPlugin::initialize()`` method: |
| 31 | + |
| 32 | +.. code-block:: php |
| 33 | +
|
| 34 | + use Sabre\DAV\ICollection; |
| 35 | + use Sabre\DAV\PropFind; |
| 36 | +
|
| 37 | + class MyDavPlugin extends ServerPlugin { |
| 38 | +
|
| 39 | + public function initialize(\Sabre\DAV\Server $server): void { |
| 40 | + // Called before per-node property handlers |
| 41 | + $server->on('preloadCollection', $this->preloadCollection(...)); |
| 42 | +
|
| 43 | + // Your usual property handlers |
| 44 | + $server->on('propFind', $this->handleGetProperties(...)); |
| 45 | + } |
| 46 | +
|
| 47 | + private function preloadCollection(PropFind $propFind, ICollection $collection): void { |
| 48 | + // Only preload when your properties have been requested to avoid unnecessary queries |
| 49 | + $requested = [ |
| 50 | + '{http://appdomain.example/ns}your-prop', |
| 51 | + '{http://appdomain.example/ns}another-prop', |
| 52 | + ]; |
| 53 | + $anyRequested = array_reduce( |
| 54 | + $requested, |
| 55 | + fn($result, $property) => $result || $propFind->getStatus($property) !== null, |
| 56 | + false |
| 57 | + ); |
| 58 | + if (!$anyRequested) { |
| 59 | + return; |
| 60 | + } |
| 61 | +
|
| 62 | + // Fetch data for the collection and its children in bulk |
| 63 | + // and cache results for use in your propFind handler |
| 64 | + $this->cache = $this->bulkLoadDataForCollection($collection); // implement your own caching |
| 65 | + } |
| 66 | +
|
| 67 | + private function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node): void { |
| 68 | + // Read and return values from $this->cache here to avoid per-node queries |
| 69 | +
|
| 70 | + // Handle per-node property loading here, to handle Depth = 0 |
| 71 | + // and cases where the `preloadCollection` event is not emitted. |
| 72 | + } |
| 73 | +
|
| 74 | + } |
| 75 | +
|
| 76 | +
|
| 77 | +Built-in examples |
| 78 | +----------------- |
| 79 | + |
| 80 | +- Tags: ``OCA\DAV\Connector\Sabre\TagsPlugin`` preloads tags and favorite |
| 81 | + info for a folder and its children. |
| 82 | +- Shares: ``OCA\DAV\Connector\Sabre\SharesPlugin`` preloads share types and |
| 83 | + sharees for items within a folder. |
| 84 | +- Comments: ``OCA\DAV\Connector\Sabre\CommentPropertiesPlugin`` preloads unread |
| 85 | + comment counts for items within a folder. |
| 86 | + |
| 87 | +Best practices |
| 88 | +-------------- |
| 89 | + |
| 90 | +- Check requested properties: Use ``$propFind->getStatus('{ns}property')`` to |
| 91 | + confirm that your properties were actually requested before querying. |
| 92 | +- Cache results: The event can fire multiple times; cache by file id or path |
| 93 | + to avoid redundant work during the same request. |
| 94 | +- Scope your preload: Only fetch data for the current collection and (at most) |
| 95 | + its direct children; avoid fetching across the whole tree. |
| 96 | +- Depth awareness: The event fires for Depth > 0; design your preload logic so |
| 97 | + that sibling PROPFIND calls don’t repeat heavy queries or perform one query |
| 98 | + per node for too many nodes. |
| 99 | + |
| 100 | +Compatibility |
| 101 | +------------- |
| 102 | + |
| 103 | +- The ``preloadCollection`` event is available starting with Nextcloud 32. |
| 104 | + Plugins listening for the event will simply not be invoked on earlier |
| 105 | + versions (no extra guards required). |
0 commit comments