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

Groundhog Laravel Package

boysfromthefactory/groundhog

Groundhog is a lightweight Laravel/PHP package from boysfromthefactory that simplifies recurring, repeatable work in your app. Drop it in, configure a few options, and keep repetitive logic clean, predictable, and easy to test. No extra dependencies.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install the package and publish its config and migrations. On a fresh app a plain php artisan migrate is enough; on an existing 0.1 install you must first run php artisan vendor:publish --tag="groundhog-migrations" and then php artisan migrate to run drop_groundhog_occurrence_index.
  • Model your recurrence as a series row (rule + duration), not as pre-generated rows. As of 0.2.0 there is no groundhog_occurrences table — occurrences only exist inside a query result.
  • First use case: take a model with a series and ask for its occurrences inside a window (e.g. "everything on the calendar this month"), ordered, paginated, with a total. That single query is now the entire contract of the package.
  • Look first at the config (groundhog.max_occurrences_per_series), then at the query scope that expands occurrences — that scope is where the JSON aggregation and driver branching live.
  • Sanity-check your driver before writing anything else: SQLite, MySQL and PostgreSQL are supported; anything else throws RecurrenceNotSupported the moment you expand a query.

Implementation Patterns

  • Always pass an explicit window. Every read is series × occurrences in window, evaluated at query time. Treat the window as a required argument in your own service/repository layer rather than letting controllers pass arbitrary ranges through.
  • Paginate with the built-in total. The expansion is passed to the DB as a single JSON parameter and unnested server-side, so you get a page plus a count in one round trip — don't fetch all occurrences and paginate in PHP.
  • Cache aggressively at the application layer. Because nothing is stored, repeated identical queries recompute from scratch. Memoize per request, and consider a short-TTL cache keyed on (model type, series version, window) for calendar views that are hit on every page load.
  • Keep duration edits cheap and lazy. Changing only a series' duration no longer triggers a rebuild; occurrence ends are computed on the next query. Invalidate your own caches on duration change and let the package do the rest.
  • Use the per-series cap as a guardrail, not a correctness tool. groundhog.max_occurrences_per_series now bounds what one series generates for one query, counting only in-window occurrences. Set it high enough for your widest legitimate window.
  • Abstract the driver check if you support multiple databases (e.g. SQLite in tests, something else in production). Feature-detect once at boot and fail fast rather than discovering it on a user request.
  • Upgrade path: publish the migrations tag, migrate, then delete any code that joined against groundhog_occurrences or read materialized_until — both are gone, and groundhog.max_materialization_ahead no longer exists in config.

Gotchas and Tips

  • Performance is now a query-time property, not a write-time one. The published numbers are 4.8–7.4 s (SQLite), 7.0–7.9 s (MySQL 8.4) and 6.8–7.1 s (PostgreSQL 18) for 1,000 daily series over a one-year window with a page of 25 plus total. That is slow. Budget for it: bound windows in the UI, avoid "all time" views, and add indexes on the series table (filter columns) since you can no longer index occurrence rows.
  • There is no read ceiling any more. That's a capability, not an invitation — a far window on many series will happily run for a long time. Enforce a maximum window span in your own request validation.
  • The per-series cap can silently truncate. If a window legitimately contains more occurrences than max_occurrences_per_series, you'll get fewer rows than expected with no error. The save-time check is unchanged, so a series that validates fine can still be clipped at query time.
  • Forgetting the upgrade migration leaves the old occurrence table and materialized_until column in place. It's a no-op on fresh installs, so a green CI run tells you nothing about a production upgrade.
  • Nothing is queryable outside the package. No occurrence table means no raw SQL joins, no reporting queries against occurrences, and no DB-level aggregates. If you had dashboards built on groundhog_occurrences, they need rewriting against the expanded query.
  • RecurrenceNotSupported is thrown at query time, not at boot or at save. Wrap expansion in a test that runs against your production driver, not just SQLite.
  • Debugging slow reads: log the window bounds, the series count for the model type, and the resulting occurrence count. The cost is essentially the product of the first two, so those three numbers explain almost every performance complaint.
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.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox