tetthys/cake

Functional & Layered Authorization for Laravel — Declarative, Composable, Testable

Maintainers

Details

github.com/tetthys/cake

Source

Issues

Installs: 10

Dependents: 0

Suggesters: 0

Security: 0

Stars: 2

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/tetthys/cake

0.1.3 2025-10-20 11:40 UTC

This package is auto-updated.

Last update: 2025-10-20 11:41:01 UTC


README

Functional & Layered Authorization for Laravel — Declarative, Composable, Testable

⚙️ Installation

composer require tetthys/cake

Laravel auto-discovers the service provider:

"extra": {
  "laravel": {
    "providers": [
      "Tetthys\\Cake\\Integration\\Laravel\\CakeServiceProvider"
    ]
  }
}

Once installed, Cake automatically registers:

  • @cakeCan / @cakeCannot Blade directives
  • cake middleware
  • global helper cakeCan()

🚀 Quick Usage

🧩 1. Define Rules

// app/Policies/PostRules.php
namespace App\Policies;

use Illuminate\Http\Request;
use Tetthys\Cake\Rule\{Rule, RuleSet, Pred, Combinators as C};

final class PostRules
{
    public function update(Request $request): RuleSet
    {
        $post = $request->route('post');

        return new RuleSet([
            new Rule(
                'OwnerOrAdmin_WhenDraft',
                C::S_or(
                    Pred::S(fn($u) => $u->id === $post->user_id),
                    Pred::S(fn($u) => in_array('admin', $u->roles, true))
                ),
                Pred::D(fn($u, $a, $o, $c) => $o->data->status === 'draft')
            ),
        ]);
    }
}
  • S → Subject condition (who)
  • D → Domain condition (when/what)
  • A RuleSet is a list of (S ∧ D) rules. If any matches → Permit, otherwise → Deny (by default)

🧱 2. Controller Integration

Use the built-in trait AuthorizesRequest.

use Tetthys\Cake\Integration\Laravel\AuthorizesRequest;
use App\Policies\PostRules;

class PostController
{
    use AuthorizesRequest;

    public function update(Request $request, Post $post)
    {
        $decision = $this->authorizeWithCake(
            $request,
            'post.update',
            $post,
            app(PostRules::class)->update($request)
        );

        // Decision implements isPermit() / isDeny()
        return response()->json(['ok' => $decision->isPermit()]);
    }
}

🪶 3. Blade Directives

@cakeCan('post.update', $post)
  <button>✏️ Edit</button>
@else
  <p>You cannot edit this post.</p>
@endcakeCan

@cakeCannot('post.update', $post)
  <p>❌ No permission</p>
@endcakeCannot

✅ Automatically infers App\Policies\PostRules@update from action name + object type. You can also pass:

  • Explicit "App\\Policies\\PostRules@update" string, or
  • A pre-built RuleSet instance.

⚡ 4. Middleware (Route-Level)

You can authorize before the controller executes.

// explicit
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('cake:post.update,App\\Policies\\PostRules@update');

or just use the shorthand — automatic inference:

// automatic inference -> App\Policies\PostRules@update
Route::put('/posts/{post}', [PostController::class, 'update'])
    ->middleware('cake:post.update');

Cake finds your route model ({post}), infers "App\Policies\PostRules@update", and denies (403) if no rule matches.

🧩 5. Helper Function

use function Tetthys\Cake\Integration\Laravel\cakeCan;

if (cakeCan('post.update', $post)) {
    // Do something only if permitted
}

This helper can take:

  • action string ('post.update')
  • model or object
  • optional RuleSet or "Class@method" string

If omitted, Cake will infer the policy automatically.

🧪 6. Testing

use Tetthys\Cake\Engine\Engine;
use Tetthys\Cake\Model\{Actor, Action, ObjectRef, Context};
use App\Policies\PostRules;

$engine = app(Engine::class);

$decision = $engine->decide(
    new Actor('u-1', ['user']),
    new Action('post.update'),
    new ObjectRef('Post', (object)['user_id' => 'u-1', 'status' => 'draft']),
    new Context(),
    app(PostRules::class)->update(request())
);

expect($decision->isPermit())->toBeTrue();

Because rules are pure functions, you can test them without HTTP or Laravel context.

🧠 Key Features

Feature Description
Declarative Express access as composable predicates, not if trees
Composable Combine S and D with AND / OR / NOT
Secure Deny-by-default — no implicit permits
Laravel-Ready Works in controllers, Blade, middleware, and helpers
Functional Stateless and testable, rule logic is pure PHP

🧾 License

MIT © Tetthys