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

Psr3 Bridge Laravel Package

casbin/psr3-bridge

Integrates Casbin's PHP enforcement library with any PSR-3 compatible logger. This bridge adapts PSR-3 loggers to Casbin's logger interface, letting you route authorization decisions, policy loading, and role manager events into your existing logging stack.

View on GitHub
Deep Wiki
Context7

Getting Started

casbin/psr3-bridge is a one-class adapter: it implements Casbin's Casbin\Log\LoggerInterface and forwards every logInfo / logError / logEnforce / logModel / logPolicy / logRole call to a Psr\Log\LoggerInterface you hand it. In a Laravel app that means "make Casbin's internal authorization chatter show up in Monolog," which is the only reason you'd install it.

Install alongside the core authorizer:

composer require casbin/casbin casbin/psr3-bridge

Before writing anything, open two files:

  1. vendor/casbin/psr3-bridge/src/ — it's essentially one file. Read the constructor and the method mapping so you know exactly which PSR-3 level each Casbin method hits.
  2. vendor/casbin/casbin/src/Log/LoggerInterface.php — this is the contract the bridge satisfies. Note whether your pinned Casbin version's logEnforce takes the $explains array; the bridge was last published in early 2022 and the interface has moved since.

The first use case is almost always debugging a failing policy check. Casbin is silent by default; once the bridge is wired in you can see the loaded model, the policies under consideration, the matcher being evaluated, and the request/result pair — which is exactly the information you're missing when Enforcer::enforce() returns false and you don't know why.

Minimal wiring, typically in a CasbinServiceProvider:

use Casbin\Bridge\Psr3\Logger as CasbinLogger;
use Casbin\Enforcer;

$this->app->singleton(Enforcer::class, function ($app) {
    $psr3 = $app->make('log')->channel('casbin');   // Illuminate\Log\Logger is PSR-3
    $logger = new CasbinLogger($psr3);
    $logger->enableLog($app->environment(['local', 'staging']));

    return new Enforcer(
        config_path('casbin/model.conf'),
        config_path('casbin/policy.csv'),
        $logger,
    );
});

Add a dedicated channel in config/logging.php so authorization noise doesn't pollute laravel.log:

'casbin' => [
    'driver' => 'daily',
    'path'   => storage_path('logs/casbin.log'),
    'level'  => 'debug',
    'days'   => 7,
],

Implementation Patterns

Singleton Enforcer with the logger baked in. Casbin's Enforcer accepts the logger as its third constructor argument. Bind the Enforcer as a singleton in the container and resolve it everywhere (app(Enforcer::class)), so the model/policy are loaded once and every authorization decision in the request lifecycle funnels through the same logger instance.

Environment-gated enablement. enableLog($enabled) is the switch. Keep it false in production — Casbin's info-level output is per-enforcement and will flood disk. Use $app->environment(['local', 'staging']) or a config('casbin.log') flag, and let the channel's level be a second safety net.

Route Casbin's logInfo to debug. The bridge maps logInfo to PSR-3 info by default, which is louder than you want. Either set the channel level to debug and accept that most Casbin lines are trace-only, or subclass the bridge (see Gotchas) and remap.

Testing with a spy or NullLogger. Because the logger is a constructor dependency, tests get a clean seam. For unit tests of policy logic, pass new NullLogger() to keep output silent. For integration tests asserting that a decision was logged, use Log::spy() and assert against the casbin channel, or bind a small in-memory PSR-3 logger that records calls.

$logger = new CasbinLogger(Log::channel('casbin'));
$enforcer = new Enforcer($model, $adapter, $logger);
$logger->enableLog(true);

$enforcer->enforce('alice', 'data1', 'read');
Log::channel('casbin')->shouldHaveReceived('debug'); // or assert on a spy

Pair with enforceEx() for explanations + logging. enforceEx() returns the matched policy as the second element of its tuple, which is far more useful than the boolean. Log the explanation yourself (via Log::withContext) and let the bridge cover the low-level model/policy dump. The two together give you "which rule matched" and "what the enforcer was thinking."

Middleware for request context. Casbin's logger methods don't receive HTTP context — no user ID, route, or request ID. Push those into Monolog's shared context with Log::withContext([...]) in middleware before the Enforcer runs, so every bridge line is automatically tagged.

Gotchas and Tips

  • The package is stale (last release Feb 2022, 2 stars, zero dependents). The adapter is trivial — if you hit an interface mismatch or a namespace you can't confirm, copy the ~50-line class into app/Support/Casbin/Psr3Logger.php, implement Casbin\Log\LoggerInterface, and delete the dependency. You lose nothing. Confirm the namespace in vendor/casbin/psr3-bridge/src/ (Casbin\Bridge\Psr3\Logger in most releases) before importing it.
  • Signature drift between Casbin versions is the #1 breakage. If your php-casbin is newer than the bridge's target, logEnforce may have an $explains fourth parameter the bridge doesn't declare, and class ... implements LoggerInterface will fatal on the method signature. Pin both in composer.json (casbin/casbin matched to the bridge's era) or subclass and override with the modern signature.
  • Silent failure mode: no logger attached. If you forget the third constructor argument, Casbin falls back to its internal default logger, not your PSR-3 one. Symptom: no casbin log lines ever appear. Check $logger->isEnabled() and that the Enforcer actually received the instance.
  • No structured contexts. The bridge logs strings. $request, $result, and $explains are stringified and lost as fields. If you need to query on them (e.g., in a log aggregator), extend the bridge and override logEnforce to emit a context array:
    public function logEnforce($matcher, $request, $result, $explains = []): void
    {
        $this->logger->debug('casbin.enforce', compact('matcher', 'request', 'result', 'explains'));
    }
    
  • logModel / logPolicy dump the entire policy set. That happens once per model load — if you're resolving the Enforcer as a singleton, it's once per request; if you're new Enforcer inside a loop, you'll dump every policy on every iteration. Another reason to bind it as a singleton in the container.
  • Don't reach for Log::channel() on every call. $app->make('log')->channel('casbin') returns a fresh Illuminate\Log\Logger wrapper each time; construct the bridge once at binding time so the channel and handler stack are reused.
  • Extension point is the bridge class itself. It's a thin composition around the PSR-3 logger with no other collaborators. Subclass it to remap levels, add context, or suppress specific Casbin methods (logModel and logPolicy are the noisy ones; logEnforce is the valuable one).
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.
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle
glavweb/extra-bundle
glavweb/api-bundle
glavweb/admin-bundle