thecsea/simple-caldav-client
A lightweight PHP CalDAV client for connecting to calendar servers like Nextcloud, iCloud, or Google Calendar. List calendars and create, update, or delete events with a simple API, installable via Composer and easy to drop into any PHP or Laravel project.
Architecture fit. The package is a thin, synchronous, cURL-based WebDAV/CalDAV client exposing a small imperative surface (connect, list calendars, fetch events, create/update/delete). That shape fits a Laravel application that performs server-side, batch calendar synchronization inside queued jobs — the 90% use case for CalDAV in a business app (mirroring a Nextcloud/Radicale/iCloud calendar into local models, or pushing app-created events outward). It does not fit real-time, event-driven, or high-fan-out multi-tenant workloads without a wrapper, because there is no PSR-18/PSR-7 abstraction and no injectable HTTP transport. In a Laravel codebase that already standardizes on Guzzle middleware (retries, logging, tracing, Http::fake() in tests), a hard-coded cURL client is an architectural step backward: you lose test doubles, centralized timeouts, and observability for free.
Integration feasibility. Low effort, high uncertainty. There is no Laravel service provider or facade, so integration is: composer require, write a config block, write a container binding, write an adapter. That is roughly a day. The uncertainty is protocol coverage, not wiring. The realistic addressable target set is self-hosted and enterprise CalDAV: Nextcloud, ownCloud, Radicale, Baïkal, SOGo, Zimbra, Fastmail, iCloud (app-specific password). It very likely does not cover OAuth2 or modern auth flows, which effectively excludes Google Workspace and Microsoft 365 — both have retired basic-auth CalDAV. If the product requirement is "sync with customer's Outlook/Google calendar," this package is a non-starter regardless of code quality.
Technical risk. Ranked:
| Risk | Severity | Notes |
|---|---|---|
| GPL-2.0 license | Critical | Copyleft. Fine for internal tools or GPL-compatible distribution; a legal blocker for shipping in a proprietary SaaS/on-prem product without review. This is the gating item, not the code. |
| Zero dependents, 21 stars | High | No ecosystem validation. No downstream projects have stress-tested it in production, so you are the first. |
| Single-maintainer bus factor | High | Fork-and-own is the realistic operating model. |
| Unverified iCalendar correctness | High | RFC 5545 serialization is where naive clients fail: 75-octet line folding, escaping of , ; \n in SUMMARY/DESCRIPTION, VTIMEZONE emission, all-day (VALUE=DATE) semantics, DST boundaries. |
Incremental sync (sync-token, RFC 6578) / ctag handling |
Medium-High | Without it every run is a full calendar pull; cost scales linearly with history. |
ETag / If-Match optimistic concurrency |
Medium | Missing or naive handling produces silent overwrites between two writers. |
| Recurring events (RRULE expansion, EXDATE, RECURRENCE-ID overrides) | Medium | Usually punted to the consumer, which is fine but must be a conscious decision. |
| No visible test suite / CI | Medium | Cannot trust correctness without writing a contract test suite against live servers. |
Key questions to resolve before commitment.
Stack fit. Assumed PHP 7.4–8.x compatible and cURL-extension dependent — verify against the current Laravel/PHP target, and specifically check for PHP 8.2+ dynamic-property deprecations and any utf8_encode/utf8_decode usage (removed in 8.4). There is no package discovery, so it enters the app as a plain library. Treat it as an infrastructure detail behind a domain-owned port.
Migration path. The single most important design decision: do not let the vendor namespace leak past the adapter.
App\Contracts\CalendarGateway with domain verbs: listCalendars(), pullChanges(string $calendarId, ?string $syncToken), upsertEvent(CalendarEvent $e, ?string $etag), deleteEvent(string $href, string $etag).TheCseaCalendarGateway implements CalendarGateway in a service provider.sabre/dav's DAV client + sabre/vobject (BSD-3, mature, battle-tested) behind the same interface. That is roughly one sprint, not a rewrite.Compatibility. Pin an exact version ("thecsea/simple-caldav-client": "x.y.z", never ^), commit composer.lock, and vendor the source into the repo if you fork. Verify TLS 1.2+ enforcement, proxy support, redirect handling on /.well-known/caldav, and self-signed-cert tolerance for on-prem Nextcloud deployments. Test against at least two real servers with different discovery behavior — Nextcloud (predictable paths) and iCloud (principal discovery required) are the useful extremes.
Sequencing.
PROPFIND, REPORT calendar-query, one create, one update, one delete.If-Match and explicit 412 Precondition Failed → re-fetch-and-retry-or-surface-conflict handling.sync-token isn't supported by the library, implement ctag short-circuiting yourself so a no-change poll is one cheap PROPFIND.Maintenance. Assume fork-and-own from day one. Budget ~0.2 FTE ongoing, or a named rotating owner, for dependency bumps, PHP-version fixes, and server-quirk patches. Pin versions and keep a short internal changelog of your patches against upstream so a future rebase or replacement is tractable.
Support. There is no vendor, no SLA, and no commercial backing — only the GitHub issue tracker. Your internal runbook must therefore own the failure taxonomy: auth, discovery, timeout, protocol violation, conflict, malformed remote data. Escalation path is "read RFC 4791 / RFC 5545," which means the on-call engineer needs real protocol literacy, not just Laravel familiarity.
Scaling. This is the package's weakest operational dimension. A synchronous cURL client issuing one HTTP round trip per event creates an N+1 over the network: a 5,000-event calendar becomes thousands of requests and many minutes of wall clock. Mitigations are structural, not library-level: run in queued jobs, chunk aggressively, use calendar-multiget REPORTs for bulk fetch, cap concurrency per remote host to stay under provider rate limits, and store the last sync-token/ctag so steady-state sync is O(changes) rather than O(calendar). Also account for full iCal bodies held in memory per chunk — keep chunk sizes modest (100–250 objects) on constrained workers.
Failure modes to design for now.
Retry-After handling → thundering herd across tenants; add jittered exponential backoff.Ramp-up. The API surface is small enough that a mid-level PHP developer is productive in 2–3 days and can demo a working sync in under a week. Trustworthy production behavior — conflicts, DST, retries, provider matrix — is 2–3 weeks including the contract test suite. That is a genuinely attractive ramp-up cost and the main reason this package is worth a spike despite the low adoption signals.
TPM recommendation. Conditional go, scoped. Approve for self-hosted/enterprise CalDAV targets only, contingent on legal clearing GPL-2.0, and only behind a CalendarGateway port that makes the dependency reversible. Do not position it as the long-term strategic calendar integration: no OAuth2 path, zero dependents, and a single maintainer mean it cannot carry a roadmap that includes Microsoft 365 or Google Workspace. Start with read-only inbound sync, keep SabreDAV + VObject pre-approved as the exit ramp, and re-evaluate at the point write-back ships.
How can I help you explore Laravel packages today?