sabre/vobject
Parse, generate, and manipulate iCalendar (RFC5545) and vCard (RFC6350) data in PHP with an easy-to-use API. sabre/vobject supports reading/writing VObject structures for calendar and contact workflows via Composer install.
Installation:
composer require sabre/vobject "^4.0"
Ensure PHP ≥7.4 (or use ^3.4 for PHP 5.5).
First Use Case:
Parse an .ics or .vcf file:
use Sabre\VObject\Component\VCalendar;
use Sabre\VObject\Component\VCard;
// Parse iCalendar
$ical = VCalendar::fromString(file_get_contents('event.ics'));
$events = $ical->getChildren(['VEVENT']);
// Parse vCard
$vcard = VCard::fromString(file_get_contents('contact.vcf'));
$name = $vcard->FN; // Access properties directly
Key Entry Points:
VCalendar, VEVENT, VTODO, VJOURNAL.VCard, FN, EMAIL, TEL, ADR.RRULE, RDATE, EXDATE (via Sabre\VObject\RecurrenceIterator).Where to Look First:
Component, Property, RecurrenceIterator).$vcard = VCard::fromString($rawVcf);
$ical = VCalendar::fromStream($icsStream);
$serialized = $vcard->serialize();
$ical->serialize($outputStream);
Forgiving mode for malformed input:
$vcard = VCard::fromString($brokenVcf, ['forgiving' => true]);
foreach ($ical->getChildren(['VEVENT']) as $event) {
$summary = $event->SUMMARY;
$dtstart = $event->DTSTART->getDateTime();
}
$event->UID = 'unique-id-123';
$event->DTSTART = '20230101T120000Z';
$event->RRULE = 'FREQ=WEEKLY;UNTIL=20231231T235959Z';
$iterator = new \Sabre\VObject\RecurrenceIterator($event);
foreach ($iterator as $instance) {
$instanceDate = $instance->DTSTART->getDateTime();
}
EXDATE):
if ($iterator->isDateInRange($date, $endDate)) {
// Date is part of the recurrence
}
$vcard->add('EMAIL', ['type' => ['WORK', 'INTERNET']], 'user@example.com');
$vcard->remove('TEL', ['type' => 'CELL']);
$emails = $vcard->EMAIL->getValues();
use Sabre\VObject\iTip\Message;
$message = new Message($event, 'REQUEST', 'organizer@example.com');
$serialized = $message->serialize();
$broker = new \Sabre\VObject\iTip\Broker();
$response = $broker->parse($rawItipMessage);
$event->DTSTART = '20230101T120000';
$event->DTSTAMP = '20230101T120000Z';
$event->VTIMEZONE = $timezoneComponent; // From \Sabre\VObject\Component\VTimezone
$dt = $event->DTSTART->getDateTime();
$dt->setTimezone(new \DateTimeZone('America/New_York'));
Store/Retrieve from Database:
$serialized = $vcard->serialize();
$vcardData = ['raw' => $serialized, 'type' => 'vcard'];
$vcard = VCard::fromString($vcardData['raw']);
API Endpoints:
return response($vcard->serialize(), 200, [
'Content-Type' => 'text/vcard',
'Content-Disposition' => 'attachment; filename="contact.vcf"',
]);
$vcard = VCard::fromString(request()->file('vcard')->getContent());
Queue Jobs for Recurrence Processing:
RecurrenceJob::dispatch($event)
->onQueue('calendar')
->delay(now()->addHour());
Validation:
$validator = Validator::make($request->all(), [
'vcard' => 'required|string',
]);
Event Listeners:
Event::listen('calendar.event.recurrence.changed', function ($event) {
// Send notifications, update UI, etc.
});
Custom Property Types:
Extend Sabre\VObject\Property for domain-specific fields:
class CustomProperty extends \Sabre\VObject\Property\Property {
public function __construct($name, $value, array $parameters = []) {
parent::__construct($name, $value, $parameters);
}
}
Recurrence Filters:
Create a decorator for RecurrenceIterator:
class FilteredRecurrenceIterator extends \Sabre\VObject\RecurrenceIterator {
public function __construct(\Sabre\VObject\Component\VEvent $event, callable $filter) {
parent::__construct($event);
$this->filter = $filter;
}
public function current() {
$event = parent::current();
return $this->filter($event) ? $event : null;
}
}
Timezone Service: Centralize timezone logic:
class TimezoneService {
public function getTimezoneComponent(string $tzId): \Sabre\VObject\Component\VTimezone {
// Fetch or create VTIMEZONE component
}
}
Timezone Quirks:
DTSTART:20230101T120000) are local time; fixed times (e.g., DTSTART:20230101T120000Z) are UTC.
Always specify TZID for floating times to avoid ambiguity.Sabre\VObject\Timezone\TimezoneConverter for accurate conversions:
$converter = new \Sabre\VObject\Timezone\TimezoneConverter();
$dt = $converter->convert($dt, 'America/New_York', 'UTC');
Recurrence Edge Cases:
RRULEs with BYMONTHDAY or BYDAY can cause infinite loops. Use UNTIL to bound ranges:
$event->RRULE = 'FREQ=YEARLY;BYMONTH=12;UNTIL=20300101T000000Z';
How can I help you explore Laravel packages today?