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

Events Calendar Bundle Laravel Package

atoolo/events-calendar-bundle

Sitepark event calendar bundle for publishing and managing events on websites, with CMS entry, web forms, database imports, and filtered search by category, keyword, and time period.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you are installing. atoolo/events-calendar-bundle is a Symfony bundle, not a Laravel package. It has zero Laravel integration surface: no extra.laravel providers, no publishable config, no facade, no Blade components. It is also part of the Sitepark Atoolo stack — it expects atoolo/resource-bundle, atoolo/graphql-search-bundle and an Atoolo search index to be present. Do not expect composer require to be the end of the story.

Two integration paths — pick one deliberately:

  1. Sidecar service (recommended). Stand up a minimal Symfony app that is the event calendar, expose its HTTP/GraphQL/iCal endpoints, and consume it from Laravel over HTTP. This keeps the Atoolo dependency graph (and its PHP/Symfony version constraints) out of your Laravel app entirely.
  2. In-process bridge (discouraged for most teams). Register the bundle's services manually inside a Laravel service provider using the Symfony container, then wire the Doctrine/GraphQL pieces by hand. This only pays off if you genuinely need PHP-level access to the Scheduling* DTOs and indexers, and it couples your Laravel upgrades to Atoolo's.

Minimal steps for path 1:

composer create-project symfony/skeleton events-calendar
cd events-calendar
composer require sitepark/atoolo-events-calendar-bundle

Then enable the bundle in config/bundles.php, add the Atoolo resource/GraphQL bundles and their config, and confirm routes with php bin/console debug:router. Run it behind your normal reverse proxy, and point Laravel at it with a base URL in config/services.php.

Look here first, in this order:

  • https://sitepark.github.io/atoolo-docs/develop/bundles/events-calendar/ — the only real documentation; the README is a product blurb.
  • bin/console debug:router in the sidecar — the iCal API routes are the most immediately useful surface (/api/ical/..., including a search variant).
  • The GraphQL schema in the sidecar — the release notes reference eventTeaser and a symbolicAsset field with a variant argument; those are your listing-page primitives.

First use case to ship: a public event list backed by the GraphQL eventTeaser, plus a per-event "add to calendar" link hitting the iCal endpoint. That exercises indexing, serialization and the asset field in one slice, and tells you fast whether the index is populated correctly.

Before you build filters, decide which date question you are asking. As of 1.18.1 the bundle explicitly distinguishes expand (turn a recurring series into concrete occurrences) from search by date (filter a result set to a window). These produce different answers for the same event and were conflated before the fix. Write down which one each screen needs — a "what's on this week" page is search-by-date, a "show me every occurrence of this series" page is expand.

Implementation Patterns

Wrap every call in one Laravel-side client. Create App\Services\EventCalendarClient with Http::baseUrl(config('services.events_calendar.url')), ->timeout(...), ->retry(...), and typed methods (upcoming(), expand(string $id), teaser(string $id), ical(string $id)). Everything else in the app — controllers, Livewire, Inertia — depends on that class, not on raw Http calls. This is your only real abstraction seam, so make it a good one.

Keep "expand" and "search by date" as separate client methods. Do not let a single events($filters) method hide the choice. A recurring event series is either one row filtered by date, or N rows of occurrences — and the 1.18.1 fix means the bundle now treats these as different operations. Mirroring that split in your client keeps the intent readable and makes the eventual behaviour change obvious at the call site rather than buried in GraphQL variables.

Treat GraphQL as the read API and Eloquent as the cache. Two workable shapes:

  • Live proxy: forward the user's filters (category, keyword, date range) straight to the GraphQL query. Simplest, but couples page latency to the index.
  • Materialized view (usually better): an artisan command pulls events on a schedule into events / event_dates tables, and your listing, filtering and sorting are plain Eloquent. You get pagination, scopes and whereDate for free, and the calendar can be down without taking the site down. If you materialize, you must decide whether you are storing series or occurrences — that is the same expand/search decision, made once at import time. Storing occurrences is usually the right call for a public listing.

If you materialize, map the bundle's Scheduling DTO faithfully: it carries a begin date/time and an optional end. Its recent history is explicitly about not defaulting endDate to beginDate — preserve that null in your schema (nullable timestamp), or you will silently turn single-day events into multi-day ones.

Use iCal as a thin, stateless endpoint. Fetch it per request or cache it briefly; it is not a data source to model against. Pass language and location as the endpoints expect, and note there are two forms: one with language in the path, one without. The occurrence parameter exists for recurring series — decide per event whether you emit "all occurrences" or "this one" and put that choice in your client method rather than in views.

Mirror the configuration, don't hardcode it. The bundle's indexer behaviour is driven by config that has changed names across 1.x releases: highlightCategory became simpleCategoryMap, and there is a separate venue category mapping. If your Laravel app filters by category, read those mappings from a config file of your own that you keep in sync with the sidecar's — do not let two divergent lists exist.

Plan for multi-instance from day one. Release 1.1.0 added multiple-instance support for both the bundle and the RCE indexer. If your Laravel app serves several brands/sites, key your client config by instance name (services.events_calendar.sites.{site}.url) rather than assuming one global URL. Retrofitting this later means touching every call site.

Keep the sidecar's GraphQL bundle in lockstep with the calendar bundle. Chores in releases routinely bump atoolo/graphql-search-bundle alongside. Bump both together in the sidecar's composer.json and commit the resulting lock — a drifted GraphQL version is the most likely source of "the schema no longer has that field" surprises from Laravel, where you have no profiler to tell you why.

Hook reindexing into your deploy and content events. Anything index-backed is stale until the sidecar reindexes. Invalidate your Laravel-side cache after any write in the CMS/admin flow, and make reindexing a step in deploy rather than something a human remembers.

Keep extensions on the Symfony side. Custom indexers, extra GraphQL fields, and category-map changes are Symfony-bundle work, committed in the sidecar repo. Do not attempt to monkey-patch bundle internals from Laravel.

Gotchas and Tips

  • No Laravel auto-discovery, and no vendor:publish. You will not find config to publish. Assume you are writing the wiring yourself and budget for it.
  • PHP floor is effectively 8.2. The 8.1 badge is annotated "no longer checked automatically"; 8.2–8.4 are tested. Check this against your Laravel version's PHP constraint before committing.
  • The 1.x line churns names. EventDate was renamed to Scheduling, symbolicImage to symbolicAsset, highlightCategory to simpleCategoryMap, and the iCal search route moved from a path segment to a query parameter. The current line is 1.18.x. Pin an exact version, and read each release note before bumping — these are breaking renames shipped as minor features.
  • Patch releases can change query semantics without changing shape. 1.18.1's "differentiate between expand and search by date" is a bug fix that changes what results come back for date-filtered and recurring queries — your HTTP contract, GraphQL field names and response structure are untouched, so nothing fails loudly. If you proxy live, expect different result sets after the bump; if you materialize, re-run your import and diff row counts before assuming it's an improvement. Re-run any snapshot tests that pin "N events in this window".
  • GraphQL version churn rides along with calendar releases. 1.18.1 bumps atoolo/graphql-search-bundle and shuffles the phpstan/phar tooling. Tooling chores are noise to you; the GraphQL bump is not. Check the sidecar's composer.lock diff, not just the calendar bundle's release notes.
  • Timezone handling is on you. Raw scheduling is parsed with gmdate, so the bundle's internal arithmetic is UTC-based. Store UTC, convert at the presentation edge, and test an event that crosses midnight in a non-UTC locale.
  • An unset end date is meaningful. Recent bug fixes specifically stopped endDate falling back to beginDate in two separate places. Audit any code (yours included) that does $end ?? $begin.
  • Filtering at index time, not query time. Inactive events, blacklisted dates and online-status changes are dropped during indexing. If the index has not been refreshed, the API will happily return events that should be gone. Treat "stale index" as a first-class failure mode with a monitoring check.
  • iCal filenames and suffixes got attention more than once. Sanitization and trailing location-suffix handling both needed fixes. Snapshot-test the URLs your client builds — an extra or missing trailing slash is a real historical bug class here.
  • Debugging is asymmetric. In the sidecar you get the Symfony profiler, debug:container, debug:router and real stack traces. From Laravel you get an HTTP status and a log line. Log the full response body (truncated) on non-2xx in your client, and build a health/ping route in the sidecar — you will need it.
  • This package has 0 stars and 0 dependents. There is no community to search for your error message. The useful signals are the E2E suite in atoolo-e2e-test, the Sitepark changelogs, and the commit diffs themselves — at this release cadence, the diff is often more informative than the one-line note attached to it. Plan for reading source, and be comfortable pinning to a known-good tag for long stretches.
  • Extension points you actually have: the bundle's config (category/venue maps, indexer settings), the GraphQL schema in the sidecar, and the public HTTP surface. Everything else is private and worth treating as such.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings