Skip to content

Middleware & Blade

Tashil ships route middleware and Blade directives that gate access declaratively — no manual checks sprinkled through your controllers. Both share one pluggable resolver that decides who the current subscriber is.

Route middleware

Three middleware register automatically on boot:

AliasRequires
subscribedThe resolved subscriber has a currently valid subscription.
plan:{slug}Subscribed and on the named plan.
feature:{slug}Subscribed and the feature check passes for the named feature.
routes/web.php
Route::middleware('subscribed')->group(function () {
    // any valid subscription
});
 
Route::middleware('plan:pro')->group(function () {
    // the Pro plan only
});
 
Route::middleware('feature:api-calls')->group(function () {
    // requires the api-calls feature on the current snapshot
});

All three abort with 403 on failure. The feature middleware honors per-type semantics — a limit feature must have remaining quota, a metered feature must have sufficient balance, a boolean feature must be truthy.

Overriding the aliases

If an alias collides with one your app already uses, rename it (or set it to null to skip registering it):

config/tashil.php
'middleware' => [
    'aliases' => [
        'subscribed' => 'has-subscription', // renamed
        'plan'       => 'plan',
        'feature'    => 'feature',
    ],
],

Blade directives

Four conditional directives gate views. They use the same resolver as the middleware, so a custom resolver affects routes and views uniformly. All support @else, and all degrade safely to false when no subscriber can be resolved (a guest, no override, or a resolved object that isn't Subscribable).

@subscribed
    {{-- a valid subscription exists (Active / OnTrial / grace) --}}
@else
    <a href="{{ route('pricing') }}">Choose a plan</a>
@endsubscribed
 
@plan('pro')
    {{-- on the Pro plan --}}
@endplan
 
@feature('api-calls')
    {{-- the feature check passes --}}
@else
    <p>Upgrade to unlock the API.</p>
@endfeature
 
@onTrial
    {{-- strictly on trial: status == OnTrial AND trial_ends_at in the future --}}
@endonTrial

The subscribable resolver

By default, the "current subscriber" is auth()->user(). That's perfect for user-scoped apps. For team- or tenant-scoped billing, override the resolver once — typically in AppServiceProvider::boot:

app/Providers/AppServiceProvider.php
use Foysal50x\Tashil\Facades\Tashil;
 
public function boot(): void
{
    Tashil::resolveSubscribableUsing(fn () => Team::current());
}

Now every middleware check and Blade directive resolves the current Team instead of the user.

Tashil::resolveSubscribable() returns null when no resolver is registered and auth()->user() is unauthenticated, or when the resolved object doesn't implement Subscribable. The middleware treat both cases as "deny → 403"; the Blade directives treat both as false.

Reset the resolver in tests

The resolver is stored statically on the Tashil facade and persists across requests within a process. In tests, call Tashil::forgetSubscribableResolver() in your teardown to avoid one test's resolver bleeding into the next.

Choosing the active subscription

When a subscriber holds more than one subscription, Tashil needs to know which one represents "the active one" for feature and lifecycle checks. By default it's the latest valid subscription. Override resolveSubscription() on your model for multi-subscription or workspace scenarios:

app/Models/Team.php
use Foysal50x\Tashil\Contracts\Subscribable;
use Foysal50x\Tashil\Models\Subscription;
use Foysal50x\Tashil\Traits\HasSubscriptions;
 
class Team extends Model implements Subscribable
{
    use HasSubscriptions;
 
    public function resolveSubscription(): ?Subscription
    {
        // Prefer the subscription tied to the team's current workspace.
        return $this->subscriptions()
            ->valid()
            ->where('package_id', $this->currentWorkspace->preferredPackageId())
            ->first();
    }
}

loadSubscription() calls resolveSubscription() exactly once per request lifecycle and memoizes the result, so every feature check and the middleware all share the same row. Don't bypass the resolver with direct repository calls — multi-subscription hosts depend on the override taking effect uniformly.

Tashil — Subscription management for Laravel. Released under the MIT license.