Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Sabredav Bundle Laravel Package

secotrust/sabredav-bundle

A Symfony bundle that integrates SabreDAV to add WebDAV, CalDAV, and CardDAV server capabilities to your application. It wires SabreDAV into Symfony’s configuration and services so you can expose calendars, contacts, and files with less boilerplate.

View on GitHub
Deep Wiki
Context7

Getting Started

Installation is the standard Symfony2 flow: composer require secotrust/sabredav-bundle (note the bundle targets Symfony 2.x and an older SabreDAV, so expect to pin versions), then register it in app/AppKernel.php with new Secotrust\Bundle\SabreDavBundle\SecotrustSabreDavBundle(). The bundle's only real job is wiring a Sabre\DAV\Server instance into the container and giving you a controller/route to dispatch WebDAV requests through Symfony's HTTP stack.

Where to look first, in order:

  1. DependencyInjection/Configuration.php — this is the authoritative list of config keys (base URI, server options, plugin/tree wiring). Anything the README is vague about is defined here.
  2. Resources/config/*.xml — the service IDs and tags you'll decorate or override. Run app/console debug:container --tag=<tag> after booting to see what the bundle registers.
  3. The controller(s) in Controller/ — this shows how a Symfony Request is handed to SabreDAV and how the Response comes back. That is the seam you'll customize most often.
  4. The README for the minimal YAML block.

Minimal first use case — serve a directory read-only:

# app/config/config.yml
secotrust_sabre_dav:
    base_uri: /dav
# app/config/routing.yml
dav:
    path: /dav/{path}
    defaults: { _controller: SecotrustSabreDavBundle:Dav:index, path: '' }
    requirements: { path: '.*' }

Then point the bundle's tree at new Sabre\DAV\FS\Directory('/path/to/files') (or a custom root collection implementing Sabre\DAV\ICollection), and verify with:

curl -i -X PROPFIND -H "Depth: 1" -u user:pass http://localhost/app_dev.php/dav/

If that returns a 207 Multi-Status with a <D:multistatus> body, the plumbing is correct and everything after this is just node and plugin work.

Implementation Patterns

The controller is a thin adapter — own it. In practice you'll copy the bundle's controller into your app or subclass it, because you almost always need to do three things it doesn't: inject your own root node, disable the profiler/toolbar for DAV routes, and control the baseUri dynamically (multitenancy, reverse proxies). A typical adapter looks like:

public function davAction(Request $request, $path)
{
    $server = $this->get('secotrust_sabredav.server'); // verify ID via debug:container
    $server->setBaseUri($request->getBaseUrl() . '/dav');
    $server->setRequest(Sabre\HTTP\Sapi::getRequest());
    $server->exec();
    // the bundle typically converts Sabre's Response to HttpFoundation here
}

Building the tree is the real work. Two patterns dominate:

  • Filesystem-backed: Sabre\DAV\FS\Directory for simple shares; wrap it in a custom ICollection to enforce ACLs per subpath.
  • ORM/database-backed: write nodes that implement Sabre\DAV\ICollection, IFile, INode. Keep them thin — a DAV node should do path resolution and delegate persistence to a repository. IFile::get() should return a stream or string; put() should accept the request body stream, not read it all into memory.

Use Sabre\DAV\ObjectTree when your node graph is fixed (a virtual root with a handful of children); use Sabre\DAV\Tree + INode::getChild() recursion when it's dynamic.

Plugins are the extension model. Register them by adding to the server in a compiler pass, a container-aware service, or a bundle boot hook:

$server->addPlugin(new Sabre\DAV\Browser\Plugin());          // dev-only web UI
$server->addPlugin(new Sabre\DAV\Locks\Plugin($lockBackend)); // enables LOCK/UNLOCK
$server->addPlugin(new Sabre\DAV\Sync\Plugin());              // RFC 6578 sync-collection

If the bundle exposes a "plugin" tag, prefer tagged services over hardcoding — it keeps config.yml declarative and lets third-party bundles contribute. Check with debug:container --tag=sabredav.plugin (exact tag name varies by version).

Authentication: bridge to Symfony security, don't reinvent it. DAV clients (Finder, Windows Explorer, Cyberduck, rclone) speak HTTP Basic. The clean pattern is a Sabre\DAV\Auth\Backend\AbstractBasic subclass that calls Symfony's AuthenticationManager or, more simply, UserProviderInterface::loadUserByUsername() plus the security.encoder_factory to verify the password. Keep the firewall for that path stateless: true and http_basic, or better, don't route it through the firewall at all and do auth entirely in the DAV backend. Mixing both leads to duplicate credential prompts and session cookies leaking into DAV responses.

Use SabreDAV events for cross-cutting behavior. beforeMethod, beforeWriteContent, afterCreateFile, afterUnbind, propFind are the right places for auditing, quota checks, and cache invalidation. Wire them in the same service that builds the server so listeners are testable in isolation.

Test at the Sabre layer, not the HTTP layer. Sabre\DAV\Server can be driven with Sabre\HTTP\Request/Response objects without booting Symfony, which makes tree/plugin logic unit-testable. Reserve curl/functional tests for the controller, base-URI, and auth-header behavior.

Gotchas and Tips

Base URI mismatch is the #1 source of bugs. SabreDAV computes hrefs from $server->getBaseUri(), and clients follow those literally. If you mount the route under a front controller (/app_dev.php/dav) or behind a proxy with a path prefix, the default base_uri from config will be wrong and clients will 404 on the second request. Set it from $request->getBaseUrl() . $request->getPathInfo()'s mount point, and make sure TrustedProxies are configured so X-Forwarded-Prefix/X-Forwarded-Host are honored. Symptom: the first PROPFIND works, browsing into a collection fails.

The Authorization header gets eaten by PHP-FPM/Apache CGI. If Basic auth "works in curl but not in the browser/client," add RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] (or the nginx equivalent) and read HTTP_AUTHORIZATION rather than PHP_AUTH_USER. This is a server config issue that will look like a bundle bug.

Symfony's Request is not Sabre's request. Methods like PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, UNLOCK pass through Symfony fine, but the method override listener and some firewalls can interfere. Ensure Request::enableHttpMethodParameterOverride() doesn't rewrite bodies for DAV verbs, and exclude the DAV route from any firewall/access-control rules you don't explicitly intend.

Don't let the profiler touch DAV responses. In dev, the web debug toolbar injects HTML into the body and the profiler wraps the kernel response. Disable both for your DAV route (or run DAV on a separate prod-like environment) — otherwise PROPFIND responses come back with a <script> appended and clients choke on the XML.

Large PUT/GET wants streaming. If the controller does $response->setContent($file->get()) or reads the body with getContent(), memory spikes on multi-GB files. Make IFile::get() return a stream resource or callable and let Sabre stream it; likewise pass the PUT body as php://input rather than buffering it. Watch out for output buffering and headers_sent() if you've echoed anywhere earlier.

LOCK/UNLOCK and dead properties need storage. Without a Sabre\DAV\Locks\Backend\BackendInterface (PDO/File) the server returns 501 Not Implemented on LOCK, and clients like Office/macOS Finder interpret that as "read-only". If you use PDO, import sabredav.sql (or the bundle's schema) and confirm the tables exist. Same for Sabre\DAV\PropertyStorage\Backend\PDO if you want PROPPATCH to persist.

Path handling and security on FS\Directory. SabreDAV sanitizes paths, but symlinks inside the share root will be followed and can escape it. Resolve with realpath() and verify the prefix, and set the share root's permissions so PHP can't write unless you intend PUT to work. Also disable Directory on NFS mounts you don't control.

Windows Explorer and macOS Finder are stricter than curl. They require a DAV: response header on OPTIONS, correct Content-Type: text/xml; charset=utf-8 on multistatus responses, and often an initial OPTIONS at /. Enabling Sabre\DAV\Browser\Plugin only in dev and testing with a real client early saves days — curl will happily accept things Finder rejects.

Version drift is the biggest long-term risk. The bundle is old (13 stars, no dependents) and pins a SabreDAV generation that predates the 3.x/4.x API. If you're modernizing, the pragmatic path is to keep the bundle for its Symfony wiring but override the sabredav.server service definition with a factory that constructs a current Sabre\DAV\Server directly; the tree, plugins, and backends you write are portable, the container glue is not. Freeze the lockfile and expect to vendor-fork the controller rather than rely on upstream fixes.

Debugging checklist when a client fails: verify the route receives the request (prod.log), verify getBaseUri() and getRequestUri() match what the client called, curl the exact method with -v, then confirm the response is well-formed XML (xmllint --format). Nine times in ten it's a base-URI or Authorization issue, not the node implementation.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log