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

Google Calendar Bundle Laravel Package

fungio/google-calendar-bundle

Symfony bundle for integrating Google Calendar into your PHP app. Wraps the Google API client with ready-made services to authenticate, manage calendars, and create or sync events, simplifying scheduling features in Symfony projects.

View on GitHub
Deep Wiki
Context7

Getting Started

Reality check first. fungio/google-calendar-bundle is a Symfony 3 bundle (last activity around the Symfony 3 era), not a Laravel package. There is no service provider, no facade, no config publish tag, and no Laravel auto-discovery. If you are on a modern Laravel (9/10/11), Composer will almost certainly refuse to install it because it pins symfony/* to ^3.x alongside an old google/apiclient. Before writing any code, decide which of two paths you are on:

  1. Legacy Symfony 3 app / isolated integration — install the bundle and use its service.
  2. Modern Laravel app — do not install it. Read Service/ as a reference implementation and re-implement the ~200 lines against a current google/apiclient. This is the path you will take 90% of the time.

Where to look first in the repo

  • DependencyInjection/Configuration.php — the authoritative list of config keys (expected shape: client_id, client_secret, redirect_uri, developer_key, possibly a default calendar_id).
  • Resources/config/services.yml — the service IDs you would inject; note they are likely private in Symfony 3.4+, so you may need an alias or public: true.
  • Service/ — the actual API surface: the class that wraps Google_Service_Calendar and exposes getCalendarEvents, insertEvent, updateEvent, deleteEvent, getCalendars-style methods.
  • README.md — usually the only documentation; treat method signatures there as approximate and verify against the class.

Minimal setup (Symfony side)

composer require fungio/google-calendar-bundle
# config.yml
fungio_google_calendar:
    client_id:     "%google_client_id%"
    client_secret: "%google_client_secret%"
    redirect_uri:  "%google_redirect_uri%"
    developer_key: "%google_api_key%"

You still need, outside the bundle: a Google Cloud project, the Calendar API enabled, an OAuth 2.0 client (or a service account with domain-wide delegation), and a stored refresh token per user. The bundle typically does not persist tokens for you — that is your job (a google_tokens table keyed by user).

First use case: read events for a date range to render a dashboard "upcoming meetings" widget. That exercises auth, timezone handling, and pagination in one shot and is easy to fake in tests.

Laravel bridge, if you must use the class directly

// app/Providers/GoogleCalendarServiceProvider.php
public function register(): void
{
    $this->app->singleton(GoogleCalendarService::class, function ($app) {
        $client = new \Google_Client();
        $client->setClientId(config('services.google.client_id'));
        $client->setClientSecret(config('services.google.client_secret'));
        $client->setRedirectUri(config('services.google.redirect_uri'));
        $client->setAccessToken(
            \Crypt::decryptString($app['auth']->user()->google_token)
        );

        return new GoogleCalendarService($client);
    });
}

Wrap it in your own interface (CalendarGateway) immediately — see Patterns.

Implementation Patterns

1. Put a seam between your app and the bundle. Never inject the bundle's service into controllers or jobs directly. Define a tiny port and adapt:

interface CalendarGateway
{
    /** @return CalendarEvent[] */
    public function between(string $calendarId, CarbonInterface $from, CarbonInterface $to): array;
    public function create(string $calendarId, CalendarEvent $event): string; // returns google event id
    public function update(string $calendarId, string $eventId, CalendarEvent $event): void;
    public function delete(string $calendarId, string $eventId): void;
}

The adapter holds the bundle service. Everything else holds your interface. This makes the inevitable rip-and-replace (see Gotchas) a one-file change, and makes tests trivial.

2. Read path — always bound the query. Google returns everything if you don't constrain it:

$events = $this->calendar->getCalendarEvents(
    $calendarId,
    $from->toRfc3339String(),   // timeMin
    $to->toRfc3339String(),     // timeMax
    ['singleEvents' => true, 'orderBy' => 'startTime', 'maxResults' => 250]
);
  • singleEvents=true + orderBy=startTime is mandatory if you want expanded recurrence instances; without it you get RRULE masters you cannot render.
  • maxResults caps at 2500 but paginate at 250 for latency; follow nextPageToken until empty.
  • Filter status === 'cancelled' — cancelled instances still appear in expanded lists.

3. Write path — store the Google event ID on your model. The single most important pattern:

$eventId = $gateway->create($calendarId, $event);
$booking->update(['google_event_id' => $eventId, 'google_calendar_id' => $calendarId]);

Then updates/deletes are idempotent and reconcilable. If google_event_id is null, it's a create; if set, it's an update. Add a sync_state enum (pending, synced, failed) and a google_etag column if you ever need conflict detection.

4. Queue every write. Google's API is a network call with a p99 you do not want in a request cycle. Dispatch a job:

class PushBookingToGoogle implements ShouldQueue
{
    public $tries = 5;
    public $backoff = [1, 5, 15, 60, 300]; // 429 / 5xx

    public function handle(CalendarGateway $gateway): void { /* create/update/delete */ }
}

For deletes, do them after your DB commit and use afterCommit on the job; a rollback after a remote create leaves an orphan event.

5. Per-tenant / per-user calendars. Pass calendarId on every call rather than relying on a bundle default — multi-tenant apps almost always end up with one calendar per customer or per resource (room, technician). Resolve the calendar ID from your own table; treat primary as a dev-only convenience.

6. Token handling is yours. The bundle expects a Google_Client that already has an access token. Pattern:

if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($user->google_refresh_token);
    $user->update(['google_token' => encrypt($client->getAccessToken()['access_token'])]);
}

Refresh tokens are long-lived but can be revoked; handle invalid_grant by marking the integration disconnected and prompting re-consent, rather than throwing a 500.

7. Timezone discipline. Normalize at the boundary:

  • Store user intent as UTC CarbonImmutable in your DB.
  • When building the event payload, emit start.dateTime with an explicit offset (2024-05-01T09:00:00+02:00) and set start.timeZone to the calendar's IANA zone.
  • Never rely on the server's default timezone; Google will happily accept it and show the wrong hour.

8. Webhooks / push notifications are not in this bundle. If you need change notifications, you set up watch channels yourself against the Calendar API and handle the X-Goog-* headers in a route. Plan for it explicitly; do not assume the bundle covers it.

9. Testing. Bind a fake CalendarGateway in the container for feature tests; for adapter tests, use a Google_Client mock or record/replay HTTP fixtures. Never let the test suite touch Google — CI will flake on quota and auth.

Gotchas and Tips

  • Symfony 3 is EOL (Nov 2021). This package is unmaintained, has 10 stars and zero dependents. Treat it as a readable reference, not a dependency. The maintenance cost of pinning an old symfony/* and an old google/apiclient will exceed the cost of copying the service class.
  • Composer will fight you. Expect google/apiclient v2-era class names (Google_Service_Calendar, Google_Service_Exception) which were renamed in v1/v2 of the modern library (Google\Service\Calendar, Google\Service\Exception). Any snippet you copy from the README will need renaming if you port it forward.
  • Private services. In Symfony 3.4+ services default to private; $container->get('fungio_google_calendar...') in a controller will throw. Inject via constructor, or add a public alias in services.yml.
  • All-day events are a special case. They use start.date (not dateTime), and the end.date is exclusive — a one-day event on May 1 has end.date = 2024-05-02. Getting this wrong shifts every all-day event by a day.
  • timeMin/timeMax must be RFC3339 with an offset or Z. 2024-05-01 alone is rejected or silently misinterpreted.
  • developer_key is not a credential. It is a quota/billing key. Auth still needs OAuth or a service account; conflating the two is the #1 configuration mistake.
  • Refresh tokens expire in OAuth "Testing" mode after 7 days. If your app is not verified/published, tokens will die weekly and look like a random bug. Publish the consent screen or use a service account with delegation for server-to-server.
  • Rate limits: ~1M queries/day project-wide and per-user throttles; bursty sync jobs get 403 rateLimitExceeded / 429. Always exponential-backoff with jitter and cap concurrency per calendar.
  • Quota project misconfiguration produces 403 ... requires a quota project on service-account calls; set quota_project_id on the client.
  • Recurring event edits need singleEvents=true to find the instance ID, and updates to a single instance require the instance's own event ID, not the master's.
  • Debugging: enable the Google client's logger ($client->setLogger($psrLogger)) at debug level. The most common failures are 401 (expired/revoked token), 403 (scope — you need https://www.googleapis.com/auth/calendar for writes, not calendar.readonly), and 404 (wrong calendarId, or the calendar isn't shared with the service account).
  • Extension point to exploit: the bundle's service is thin. The cleanest long-term move is to fork the one service class into app/Services/GoogleCalendar, drop the bundle, and depend on google/apiclient directly. You keep the ergonomics, you lose the Symfony 3 anchor, and you gain access to syncToken incremental sync, watch channels, and freebusy — none of which this bundle exposes.
  • Migration budget: estimate half a day to port the service and one to two days to build proper token storage and a sync job. That is cheaper than the first time Composer blocks a Laravel upgrade because of this package's symfony/* constraint.
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.
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle
adachsoft/api-version-bundle
elsayed85/laravel-api-exceptions
ouhssini/laravel-demo-mode
daworks/ncloud-outbound-mailer-driver
gp247/cms
ignibyte/aic-gates
nexa-merchant/blog
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
cgv/localized-resource