Upgrading from SabreDAV 1.8 to 2.0
SabreDAV 2.0 has finally been released after almost 2 years of work.
New stuff
- Support for WebDAV-Sync (rfc6578).
- Support for managing calendar subscriptions. This is a proprietary extension that's supported by at least iCal, iOS and BusyCal.
- Support for jCal. A CalDAV client can now request to receive jCal, which is a json-based serialization for iCalendar.
- Added: A BasicCallback authentication backend. This authentication backend uses a callback to verify a username + password, making it extremely easy to set this up.
- Now ships with sabre/vobject 3.
- Gave the 'HTTP' package an overhaul, and moved this into a separate project. See the project page for more info.
- The event/plugin system also spliced off into a separate package. more info.
- The ICSExportPlugin got a lot of changes that made it easier for browser-based javascript applications to integrate.
- The property system got a massive overhaul, allowing for a new 'PropertyStorage' plugin. This plugin allows storage of arbitrary properties anywhere on your DAV server.
- Overall both Card- and CalDAV should be much, much faster, and consume less resources due to the addition of caldav-sync, internally optimizing multiget and database denormalization.
- Better documented and better having PATCH support.
- A brand new browser plugin that shows a lot more debugging information and looks much better.
To read the full list of changes (there's quite a few!) read the changelogs:
API Changes and BC breaks
Now requires PHP 5.4
If you are still running PHP 5.3, you must not upgrade SabreDAV. SabreDAV 1.8 will be maintained for bugfixes for some time, but you should consider upgrading your PHP version to PHP 5.4, to take advantage of the latest and greatest, and get a big speed-up in the process.
Database changes for PDO backends
The PDO backends got a number of changes, mainly to speed things up, but also to provide support for new features.
This affects you if you were running either a CalDAV or a CardDAV server, with the default PDO backend.
Before you start the migration process, please make a backup first! No warranty is given for lost data.
To run the database upgrade, simply start ./bin/migrate20.php
from the SabreDAV
project. This script will give you more information about its arguments.
Taking advantage of WebDAV-sync
This affects you if you were running a Cal or CardDAV server with the default PDO backend.
This is an optional step, but after upgrading the database schema, you can add the following line to your server file to turn on WebDAV-sync:
$server->addPlugin(new \Sabre\DAV\Sync\Plugin());
The result is much faster synchronization with modern Cal and CardDAV clients. For high-load systems, you should see significant improvements in CPU, Memory and bandwidth usage.
Upgraded to sabre/vobject 3
If you've done any work with sabre/vobject, and you should also read the migration instructions from the vobject project page.
Since this SabreDAV version we are now shipping sabre/vobject 3, which has a large amount of improvements over sabre/vobject 2, but also breaks a few things.
Event / Plugin system got a massive overhaul
This affects you if you wrote your own plugins, or if you used
subscribeEvent
or broadcastEvent
manually.
SabreDAV now externalized the event system into a separate project, namely sabre/event.
In simple terms this means that:
Sabre\DAV\Server::broadcastEvent
got renamed to::emit
.Sabre\DAV\Server::subscribeEvent
got renamed to::on
.
The arguments to these two functions have stayed the same, a bunch more event-related methods have been added, as documented on the sabre/event project page.
However, because we had to change this, we also took the chance to change the arguments of a lot of SabreDAV events, to make them much more effective.
A lot of events had a $method
and a $uri
argument. I was unhappy with this
design, as for any other information related to the HTTP request, aside from the
method or uri had to be grabbed from $server->httpRequest
.
Similarly, any information sent back to the client always had to be done
through $server->httpResponse
.
Both these properties still exists, but it is not longer the recommended way
to read information about http requests and change the http response. Instead,
event handlers get a full $request
and $response
objects as their direct
arguments.
This allows for much easier unit testing, as well as event handlers to do live-rewriting or sub-requests without affecting the global state.
The main events that have changed their behavior are 'beforeMethod' and 'unknownMethod'. 'unknownMethod' has been completely removed, and 'beforeMethod' has altered.
The implication is that every single plugin that implemented those events
has now also changed. Besides using on
instead of subscribeEvent
, the
bodies for the event handlers have been changed to use new arguments.
More on the method-handling process in the next chapter.
Switched to PSR-4
We changed out autoloading mechanism to switch from psr-0 to psr-4.
If you used vendor/autoload.php
this makes absolutely no difference, but if
you manually set-up autoloading, be aware that the directory structure is now:
lib/
lib/DAV
lib/DAVACL
lib/CalDAV
lib/CardDAV
And no longer:
lib/
lib/Sabre
lib/Sabre/DAV
lib/Sabre/DAVACL
lib/Sabre/CalDAV
lib/Sabre/CardDAV
Removed deprecated autoloader script
lib/Sabre/autoload.php
was already deprecated in version 1.7.0, but now it
got removed. If you were still using this, switch to vendor/autoload.php
.
Events related to HTTP method handling
The beforeMethod event has changed quite a bit, and the unknownMethod event had been completely removed.
When a HTTP method is executed on a SabreDAV server, for example the 'GET' method, the following events are triggered in order:
- beforeMethod:GET
- beforeMethod
- method:GET
- method
- afterMethod:GET
- afterMethod
By default the CorePlugin
will register an event handler for method:GET
.
If you'd like to handle GET in certain cases, you can be first by registering
your own GET method handler, with a lower priority.
The default priority for every method is 100, so by specifying 90 we guarantee that our event is always triggered first.
use
Sabre\HTTP\RequestInterface,
Sabre\HTTP\ResponseInterface;
$myGetHandler = function(RequestInterface $request, ResponseInterface $response) {
// If the filename contains the word cat, we will return miaow instead of
// the real response body.
$path = $request->getPath();
if (false === strpos($path, 'cat')) {
// No cat = do nothing
return;
}
// There was a cat in the url. Lets do weird stuff.
$response->setBody('Miaow!');
$response->setStatus(200); // 200 OK
$response->setHeader('Content-Type', 'text/plain');
// This ensures that the standard GET handler does not kick in.
return false;
};
$server->on('beforeMethod:GET', $myGetHandler, 90);
Every method-related handlers get 2 arguments, the http request and the http response.
beforeMethod allows you to prevent a method from being executed. The ACL method uses this to ensure that methods are not handled if the user did not have permission to do so.
beforeMethod also allows you to rewrite the response body before its being handled, for whatever reason. You may want to convert the format of the response body to something else.
The method
and method:GET
events (for the PUT method, it would be
method:PUT
) are strictly reserved for actually handling the event. If you did
handle the event, you must return false
from your event handler to indicate
that the method is handled, and no other system has to do any handling.
The afterMethod event is to do any post-processing. You could use this to log that the method has been executed, or re-write the response body after it has been generated.
CalDAV BackendInterface adds a new method
This affects you, if you wrote your own CalDAV backend.
The CalDAV BackendInterface now adds the getMultipleCalendarObjects
method.
Implementing this can result in significant speed-ups.
If you extended AbstractBackend
, there already is a default implementation,
that lazily falls back on calling getCalendarObject
.
CardDAV BackendInterface adds a new method
This affects you, if you wrote your own CardDAV backend.
The CardDAV BackendInterface now adds the getMultipleCards
method.
Implementing this can result in significant speed-ups.
If you extended AbstractBackend
, there already is a default implementation,
that lazily falls back on calling getCard
.
CalDAV and CardDAV PDO backends now have support for WebDAV-Sync
Support for WebDAV-Sync means that every change that's happening in any calendar or addressbook is now recorded.
This allows a client to simply ask for all modifications since the last sync, and greatly improves performance on a number of fronts.
If you made custom modifications to the PDO backends, keep in mind that because of this new functionality, some stuff may not work exactly liked it used to. If for example the methods that create or modify calendar objects don't register a change in the database, a client may never see this change.
Similarly, if you made modifications manually in the database, these may also be 'lost changes'. The best recommendation I can give is to have a good look at the new PDO backends and see what's going on there.
Property updating has changed.
This is relevant to you if you:
- Created a custom node that implements
Sabre\DAV\IProperties
- Created a plugin that uses the
updateProperties
event. - Created a custom backend for CalDAV, CardDAV or ACL that supports updating of properties.
The way property updating was implemented, did not allow for certain types of operations. In particular, it was impossible to write a 'generic dead property' storage.
Before, any method that dealt with updating properties would simply receive an
argument called $properties
or sometimes $mutations
that was a simple
structure such as this:
array(
'{DAV:}displayname' => 'new name!',
'{DAV:}foo' => null,
);
This had to be changed, as different components may be responsible for handling storage of different properties, but each must successful and work together for the entire operation to be successful.
To do this, a new object is introduced, Sabre\DAV\PropPatch
. This object
holds the mutation list for properties, and provides a whole bunch of
convenience methods for the statuses.
The full documentation can be read on Properties, but here's the gist of what has changed:
Sabre\DAV\IProperties::updateProperties
(now::propPatch
).- Any principal backend implementing
updatePrincipal
. - Any CalDAV backend implementing
updateCalendar
. - Any CardDAV backend implementing
updateAddressBook
.
All of the previous methods received the $mutations
or $properties
array.
This is now the PropPatch object.
Here's a new example of a node storing {DAV:}displayname
:
function propPatch(\Sabre\DAV\PropPatch $propPatch) {
$propPatch->handle('{DAV:}displayname', function($value) {
// Store the $value in the database here.
// Return true if you were successful.
return true;
});
}
As you can see updating properties now happens in two steps:
- You tell the system you promise to handle storage of
{DAV:}displayname
. - You do the actual storing (in the enclosed function).
The new PROPPATCH
system now checks if every incoming property can be stored
by some subsystem, and only then calls all callbacks.
If you want to store more than one property in one go, this can also be done using the handle method.
This is an example for a CalDAV backend that stores both the displayname, and the calendar description:
function updateCalendar($calendarId, \Sabre\DAV\PropPatch $propPatch) {
$properties = [
'{DAV:}displayname',
'{urn:ietf:params:xml:ns:caldav}calendar-description',
];
$propPatch->handle($properties, function($mutations) {
// $mutations contains the list of properties we need to update.
// Return true if you were successful.
return true;
});
}
As you can see in the previous example, the handle()
method can be used to
do multiple properties at once by supplying the list of properties as an array.
The updateProperties
event is no more
The updateProperties
event on the server is now called propPatch
, and
looks like this:
$server->on('propPatch', function($path, \Sabre\DAV\PropPatch $propPatch) {
});
beforeGetProperties
and afterGetProperties
and updateProperties
events have been removed.
These three events are now replaced by two new events:
- propFind
- propPatch
Usage instructions can be found on the writing plugins documentation.
Sabre\DAV\IProperties
has a new API
If you implement Sabre\DAV\IProperties
or ever created a tree node that
implemented an updateProperties()
method, pay attention.
The updateProperties()
method has been removed in favor of a new
propPatch()
method.
This was needed to allow the creation of a generic property storage module.
The old method signature:
function updateProperties(array $mutations) {
// ...
}
The new method signature:
function propPatch(\Sabre\DAV\PropPatch $propPatch) {
// ...
}
Instead of a simple array with to-be-updated properties, the method now
receives an instance of Sabre\DAV\PropPatch
.
Instead of having to loop through the old array and figure out what properties you support and store, there's a bunch of convenience methods that will help with this.
In particular, the handle()
method allows you to tell PropFind "I can handle
this property and I promise to store it somewhere".
For example, if your node supports storing the {DAV:}displayname
property,
this is how that method may look like:
function propPatch(\Sabre\DAV\PropPatch $propPatch) {
$propPatch->handle('{DAV:}displayname', function($value) {
// Store $value somewhere. Note that if value is null, you should
// delete the property.
//
// You must return true or false depending on if the operation was
// successful.
});
}
Updated updateCalendar
and updateAddressBook
in CalDAV and CardDAV backends.
Due to the upgraded property system, both updateCalendar and updateAddressBook
got similar changes. Each now receive a propPatch
object instead of an array
with mutations.
Updates in the sabre/http API
The lib/Sabre/HTTP portion of the library moved to its own package, and got an almost 100% rewrite.
If you did anything directly with Sabre\HTTP
, it's likely that things have
changed. This is most likely true if you ever created an instance of a class in
the Sabre\HTTP
namespace, you did something with Authentication or something
with the Request
and Response
objects.
The Request object
Old syntax:
$request = new \Sabre\HTTP\Request();
new syntax:
$request = \Sabre\HTTP\Sapi::getRequest();
If you ever created a 'fake' or 'mock' http request, you probably used a syntax such as the following:
$request = new \Sabre\HTTP\Request([
'REQUEST_URI' => '/foo',
'REQUEST_METHOD' => 'POST',
'HTTP_CONTENT_TYPE' => 'application/xml',
]);
The new syntax looks like this:
$request = \Sabre\HTTP\Sapi::createFromServerArray([
'REQUEST_URI' => '/foo',
'REQUEST_METHOD' => 'POST',
'HTTP_CONTENT_TYPE' => 'application/xml',
]);
But.. this is still a bit ugly, as it requires you to manually re-construct
the cgi variables from $_SERVER
. The new, improved syntax looks like this:
$request = new Request(
'POST',
'/foo',
['Content-Type' => 'application/xml']
);
The Response object
Before, the response object would automatically call header()
and send the
response body as soon as possible.
This is no longer true, and some methods also have changed.
Before you would do something like this:
$response = new \Sabre\HTTP\Response();
$response->setHeader('Content-Type', 'application/xml'); // This would already call php's header() !
$response->sendStatus(201); // This also calls php's header()
$response->sendBody('<?xml version="1.0"?>...etc'); // This calls fpassthru/echo
The new response object doesn't send anything right away. Everything is buffered, until sendResponse() is called. In addition, sendStatus is now setStatus, and sendBody is setBody.
$response = new \Sabre\HTTP\Response();
$response->setHeader('Content-Type', 'application/xml');
$response->setStatus(201);
$response->setBody('<?xml version="1.0"?>...etc');
\Sabre\HTTP\Sapi::sendResponse($response);
Authentication
The authentication-related classes have moved into their own namespace. The new class names are:
Sabre\HTTP\AbstractAuth
is nowSabre\HTTP\Auth\AbstractAuth
.Sabre\HTTP\BasicAuth
is nowSabre\HTTP\Auth\Basic
.Sabre\HTTP\DigestAuth
is nowSabre\HTTP\Auth\Digest
.Sabre\HTTP\AWSAuth
is nowSabre\HTTP\Auth\AWS
.
Furthermore, the constructor arguments have changed for all of these. The following 3 arguments are now required:
$realm
$request
$response
$realm
is just a single string. $request
must be a Request object, and
$response
and instance of Response.
Custom WebDAV serialization slightly changed
If you implemented your own custom, complex WebDAV properties, using
Sabre\DAV\Property
as a base-class, this affects you.
The unserialize
method now got an extra argument. The full signature changed
from static function unserialize(\DOMElement)
to
static function unserialize(\DOMElement, array $propertyMap)
. The new
$propertyMap
argument contains the list of WebDAV properties and which classes
they should be mapped to.
All you likely have to do, is to just add that new argument to the method declaration, to ensure PHP doesn't emit any warnings.
Functionality moved from the Server class to CorePlugin
This version of SabreDAV adds a 'CorePlugin', which uses the plugin system to provide basic functionality. The plugin is automatically added.
It does mean that a number of functions have moved from Sabre\DAV\Server to this plugin.
If you sub-classed Sabre\DAV\Server, or called any of these methods manually for some reason, your code will break.
The relevant methods are :
httpGet
httpOptions
httpHead
httpDelete
httpPropfind
httpPropPatch
httpPut
httpMkcol
httpMove
httpCopy
httpReport
Removed Version-related constants
Sabre\DAV\Version::STABILITY
no longer exists. The stability is now
incorporated in the VERSION constant.
Furthermore, the following classes have been removed, as they mostly contained redundant information:
Sabre\CalDAV\Version
Sabre\CardDAV\Version
Sabre\DAVACL\Version
Changed Sabre\DAV\Server::getCopyMoveInfo
This method now requires a RequestInterface
object as its first argument.
This object will be used to figure out all the relevant information, rather
than the global SabreDAV state.
Sabre\DAV\URLUtil has moved
The new classname for this utility is Sabre\HTTP\URLUtil.
The old class still exists, but will be removed in a future version.
Sabre\DAV\Server::NODE constants are removed.
The Sabre\DAV\Server::NODE_FILE
and Sabre\DAV\Server::NODE_COLLECTION
no
longer exist.