---
title: "Querying — Lifecycle for Laravel | Roundly"
description: "Query scopes for state, expiry, grace, freezes and time in state — plus withLifecycle() eager loading to avoid N+1 queries."
url: https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel/querying
language: en
---

[All packages](https://roundly-consulting.com/open-source.md)

[Lifecycle for Laravel](https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel.md)

# Querying

HasLifecycle adds query scopes for the state column and for the package’s records and schedules:

```php
Listing::query()->whereState(ListingStatus::Active)->get();
Listing::query()->whereState([ListingStatus::Active, ListingStatus::Closed])->get();
Listing::query()->whereNotState(ListingStatus::Archived)->get();
Listing::query()->whereExpiringWithin('3 days')->get();       // expires after now, within 3 days
Listing::query()->whereExpired()->get();                       // expiry passed (in grace or not yet swept)
Listing::query()->whereNotExpired()->get();
Listing::query()->whereInGrace()->get();
Listing::query()->whereFrozen()->get();
Listing::query()->whereInStateFor('14 days')->get();           // entered the current state at least 14 days ago
Listing::query()->withLifecycle()->paginate();                 // eager-load records, open schedules and latest history rows

Order::query()->whereState(PaymentStatus::Captured, 'payment_status')->get();
```

| Scope | Selects |
| --- | --- |
| `whereState($states, ?$lifecycle)` | Rows in one of the states (a state or an array). |
| `whereNotState($states)` | The complement. |
| `whereExpired()` | The expiry has passed (in grace, or not yet swept). |
| `whereNotExpired()` | No passed pending expiry. |
| `whereExpiringWithin('3 days')` | Expires after now, within the interval. |
| `whereInGrace()` | Expired, but the expiry transition is not due yet. |
| `whereFrozen()` | Frozen right now. |
| `whereInStateFor('14 days')` | Entered the current state at least that long ago. |
| `withLifecycle()` | Eager-loads the state records, open schedules and latest history rows. |

States are checked against the definition; an undeclared state throws UnknownStateException. Each scope takes an optional lifecycle name (default: the model’s first lifecycle). whereState() and whereNotState() filter the model’s own column, which is the source of truth; the expiry, freeze and dwell scopes are EXISTS subqueries on the package tables that combine with your other scopes and with SoftDeletes.

## Avoiding N+1

Handle reads (enteredAt(), version(), isFrozen(), expiresAt(), scheduled(), lastTransition(), …) use the eager-loaded relations when present and query otherwise. LifecycleResource also accepts a model directly, so a list reads the state, freeze, expiry and last transition without a query per model:

```php
// A list: a model stands for its primary lifecycle, with the actor from auth
return LifecycleResource::collection(Listing::query()->withLifecycle()->get());
```

allowedTransitions() still runs the guard pipeline per subject, so guards, Gate policies and quota counts may run their own queries. Index your lifecycle column together with quota scope columns.

## Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

[More ways to support, including crypto](https://roundly-consulting.com/support-us.md)

By donating, you agree to our [donation terms](https://roundly-consulting.com/donation-terms.md).

[Support our open source work (opens in a new tab)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Join us on Patreon (opens in a new tab)](https://www.patreon.com/cw/roundly)

## Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.

[Get a quote in 48 hours](https://roundly-consulting.com/contact.md) [Browse all packages](https://roundly-consulting.com/open-source/docs/lifecycle-for-laravel.md)
