• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

php-casbin / php-casbin / 15422853167

03 Jun 2025 04:34PM UTC coverage: 94.307% (-0.2%) from 94.485%
15422853167

push

github

leeqvip
refactor: Upgrade phpstan to level 8

52 of 63 new or added lines in 8 files covered. (82.54%)

2 existing lines in 1 file now uncovered.

1938 of 2055 relevant lines covered (94.31%)

231.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.7
/src/CoreEnforcer.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Casbin;
6

7
use Casbin\Effector\{DefaultEffector, Effector};
8
use Casbin\Exceptions\{CasbinException, EvalFunctionException, InvalidFilePathException};
9
use Casbin\Log\Logger;
10
use Casbin\Log\Logger\DefaultLogger;
11
use Casbin\Model\FunctionMap;
12
use Casbin\Model\Model;
13
use Casbin\Persist\Adapter;
14
use Casbin\Persist\Adapters\FileAdapter;
15
use Casbin\Persist\FilteredAdapter;
16
use Casbin\Persist\{Watcher, WatcherEx};
17
use Casbin\Rbac\DefaultRoleManager\ConditionalDomainManager as DefaultConditionalDomainManager;
18
use Casbin\Rbac\DefaultRoleManager\ConditionalRoleManager as DefaultConditionalRoleManager;
19
use Casbin\Rbac\DefaultRoleManager\RoleManager as DefaultRoleManager;
20
use Casbin\Rbac\DefaultRoleManager\DomainManager as DefaultDomainManager;
21
use Casbin\Rbac\{ConditionalRoleManager, RoleManager};
22
use Casbin\Util\{BuiltinOperations, Util};
23
use Closure;
24
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
25
use function sprintf;
26

27
/**
28
 * Class CoreEnforcer
29
 * The main interface for authorization enforcement and policy management.
30
 *
31
 * @author techlee@qq.com
32
 */
33
class CoreEnforcer
34
{
35
    /**
36
     * Model path.
37
     *
38
     * @var string
39
     */
40
    protected string $modelPath;
41

42
    /**
43
     * Model.
44
     *
45
     * @var Model
46
     */
47
    protected Model $model;
48

49
    /**
50
     * FunctionMap.
51
     *
52
     * @var FunctionMap
53
     */
54
    protected FunctionMap $fm;
55

56
    /**
57
     * Effector.
58
     *
59
     * @var Effector
60
     */
61
    protected Effector $eft;
62

63
    /**
64
     * Adapter.
65
     *
66
     * @var Adapter|null
67
     */
68
    protected ?Adapter $adapter;
69

70
    /**
71
     * Watcher.
72
     *
73
     * @var Watcher|null
74
     */
75
    protected ?Watcher $watcher;
76

77
    /**
78
     * RmMap.
79
     *
80
     * @var array<string, RoleManager>
81
     */
82
    protected array $rmMap;
83

84
    /**
85
     * CondRmMap.
86
     *
87
     * @var array<string, ConditionalRoleManager>
88
     */
89
    protected array $condRmMap;
90

91
    /**
92
     * $enabled.
93
     *
94
     * @var bool
95
     */
96
    protected bool $enabled;
97

98
    /**
99
     * $autoSave.
100
     *
101
     * @var bool
102
     */
103
    protected bool $autoSave;
104

105
    /**
106
     * $autoBuildRoleLinks.
107
     *
108
     * @var bool
109
     */
110
    protected bool $autoBuildRoleLinks;
111

112
    /**
113
     * $autoNotifyWatcher.
114
     *
115
     * @var bool
116
     */
117
    protected bool $autoNotifyWatcher;
118

119
    /**
120
     * $logger.
121
     *
122
     * @var Logger
123
     */
124
    protected Logger $logger;
125

126
    /**
127
     * Enforcer constructor.
128
     * Creates an enforcer via file or DB.
129
     * File:
130
     * $e = new Enforcer("path/to/basic_model.conf", "path/to/basic_policy.csv")
131
     * MySQL DB:
132
     * $a = DatabaseAdapter::newAdapter([
133
     *      'type'     => 'mysql', // mysql,pgsql,sqlite,sqlsrv
134
     *      'hostname' => '127.0.0.1',
135
     *      'database' => 'test',
136
     *      'username' => 'root',
137
     *      'password' => '123456',
138
     *      'hostport' => '3306',
139
     *  ]);
140
     * $e = new Enforcer("path/to/basic_model.conf", $a).
141
     *
142
     * @param string|Model|null $model
143
     * @param string|Adapter|null $adapter
144
     * @param Logger|null $logger
145
     * @param bool|null $enableLog
146
     *
147
     * @throws CasbinException
148
     */
149
    public function __construct(string|Model|null $model = null, string|Adapter|null $adapter = null, ?Logger $logger = null, ?bool $enableLog = null)
150
    {
151
        $this->logger = $logger ?? new DefaultLogger();
1,260✔
152

153
        if (!is_null($enableLog)) {
1,260✔
154
            $this->enableLog($enableLog);
20✔
155
        }
156

157
        if (is_null($model) && is_null($adapter)) {
1,260✔
158
            return;
20✔
159
        }
160

161
        if (is_string($model)) {
1,240✔
162
            if (is_string($adapter) || is_null($adapter)) {
1,200✔
163
                $this->initWithFile($model, $adapter ?? '');
1,160✔
164
            } else if ($adapter instanceof Adapter) {
40✔
165
                $this->initWithAdapter($model, $adapter);
504✔
166
            }
167
        } else if ($model instanceof Model) {
40✔
168
            if ($adapter instanceof Adapter || is_null($adapter)) {
40✔
169
                $this->initWithModelAndAdapter($model, $adapter);
40✔
170
            } else {
171
                throw new CasbinException('Invalid parameters for enforcer.');
16✔
172
            }
173
        } else {
174
            throw new CasbinException('Invalid parameters for enforcer.');
×
175
        }
176
    }
177

178
    /**
179
     * Initializes an enforcer with a model file and a policy file.
180
     *
181
     * @param string $modelPath
182
     * @param string $policyPath
183
     *
184
     * @throws CasbinException
185
     */
186
    public function initWithFile(string $modelPath, string $policyPath): void
187
    {
188
        $adapter = new FileAdapter($policyPath);
1,160✔
189
        $this->initWithAdapter($modelPath, $adapter);
1,160✔
190
    }
191

192
    /**
193
     * Initializes an enforcer with a database adapter.
194
     *
195
     * @param string $modelPath
196
     * @param Adapter $adapter
197
     *
198
     * @throws CasbinException
199
     */
200
    public function initWithAdapter(string $modelPath, Adapter $adapter): void
201
    {
202
        $m = Model::newModelFromFile($modelPath);
1,210✔
203
        $this->initWithModelAndAdapter($m, $adapter);
1,210✔
204

205
        $this->modelPath = $modelPath;
1,200✔
206
    }
207

208
    /**
209
     * InitWithModelAndAdapter initializes an enforcer with a model and a database adapter.
210
     *
211
     * @param Model $m
212
     * @param Adapter|null $adapter
213
     */
214
    public function initWithModelAndAdapter(Model $m, ?Adapter $adapter): void
215
    {
216
        $this->adapter = $adapter;
1,250✔
217
        $this->model = $m;
1,250✔
218
        $this->model->setLogger($this->logger);
1,250✔
219
        $this->model->printModel();
1,250✔
220

221
        $this->fm = Model::loadFunctionMap();
1,250✔
222

223
        $this->initialize();
1,250✔
224

225
        // Do not initialize the full policy when using a filtered adapter
226
        $ok = $this->adapter instanceof FilteredAdapter ? $this->adapter->isFiltered() : false;
1,250✔
227

228
        if (!is_null($this->adapter) && !$ok) {
1,250✔
229
            $this->loadPolicy();
1,210✔
230
        }
231
    }
232

233
    /**
234
     * Sets the current logger.
235
     *
236
     * @param Logger $logger
237
     */
238
    public function setLogger(Logger $logger): void
239
    {
240
        $this->logger = $logger;
10✔
241
        $this->model->setLogger($this->logger);
10✔
242
        foreach ($this->rmMap as $rm) {
10✔
243
            $rm->setLogger($this->logger);
10✔
244
        }
245
        foreach ($this->condRmMap as $rm) {
10✔
246
            $rm->setLogger($this->logger);
×
247
        }
248
    }
249

250
    /**
251
     * Initializes an enforcer with a database adapter.
252
     */
253
    protected function initialize(): void
254
    {
255
        $this->rmMap = [];
1,260✔
256
        $this->condRmMap = [];
1,260✔
257
        $this->eft = new DefaultEffector();
1,260✔
258
        $this->watcher = null;
1,260✔
259

260
        $this->enabled = true;
1,260✔
261
        $this->autoSave = true;
1,260✔
262
        $this->autoBuildRoleLinks = true;
1,260✔
263
        $this->autoNotifyWatcher = true;
1,260✔
264
        $this->initRmMap();
1,260✔
265
    }
266

267
    /**
268
     * Reloads the model from the model CONF file.
269
     * Because the policy is attached to a model, so the policy is invalidated and needs to be reloaded by calling LoadPolicy().
270
     *
271
     * @throws CasbinException
272
     */
273
    public function loadModel(): void
274
    {
275
        $this->model = Model::newModelFromFile($this->modelPath);
10✔
276
        $this->model->printModel();
10✔
277
        $this->fm = Model::loadFunctionMap();
10✔
278

279
        $this->initialize();
10✔
280
    }
281

282
    /**
283
     * Gets the current model.
284
     *
285
     * @return Model
286
     */
287
    public function getModel(): Model
288
    {
289
        return $this->model;
50✔
290
    }
291

292
    /**
293
     * Sets the current model.
294
     *
295
     * @param Model $model
296
     */
297
    public function setModel(Model $model): void
298
    {
299
        $this->model = $model;
20✔
300
        $this->fm = $this->model->loadFunctionMap();
20✔
301

302
        $this->initialize();
20✔
303
    }
304

305
    /**
306
     * Gets the current adapter.
307
     *
308
     * @return Adapter|null
309
     */
310
    public function getAdapter(): ?Adapter
311
    {
312
        return $this->adapter;
20✔
313
    }
314

315
    /**
316
     * Sets the current adapter.
317
     *
318
     * @param Adapter $adapter
319
     */
320
    public function setAdapter(Adapter $adapter): void
321
    {
322
        $this->adapter = $adapter;
40✔
323
    }
324

325
    /**
326
     * Sets the current watcher.
327
     *
328
     * @param Watcher $watcher
329
     */
330
    public function setWatcher(Watcher $watcher): void
331
    {
332
        $this->watcher = $watcher;
110✔
333
        $this->watcher->setUpdateCallback(fn() => $this->loadPolicy());
110✔
334
    }
335

336
    /**
337
     * Gets the current role manager.
338
     *
339
     * @return RoleManager
340
     */
341
    public function getRoleManager(): RoleManager
342
    {
343
        return $this->rmMap['g'];
80✔
344
    }
345

346
    /**
347
     * Gets the current role manager.
348
     *
349
     * @param RoleManager $rm
350
     */
351
    public function setRoleManager(RoleManager $rm): void
352
    {
353
        $this->rmMap['g'] = $rm;
×
354
    }
355

356
    /**
357
     * Sets the current effector.
358
     *
359
     * @param Effector $eft
360
     */
361
    public function setEffector(Effector $eft): void
362
    {
363
        $this->eft = $eft;
×
364
    }
365

366
    /**
367
     * Clears all policy.
368
     */
369
    public function clearPolicy(): void
370
    {
371
        $this->model->clearPolicy();
60✔
372
    }
373

374
    /**
375
     * Reloads the policy from file/database.
376
     */
377
    public function loadPolicy(): void
378
    {
379
        $newModel = $this->loadPolicyFromAdapter($this->model);
1,240✔
380
        if (!is_null($newModel)) {
1,230✔
381
            $this->applyModifiedModel($newModel);
1,150✔
382
        }
383
    }
384

385
    /**
386
     * Loads policy from the current adapter.
387
     *
388
     * @param Model $baseModel
389
     *
390
     * @return Model|null
391
     */
392
    public function loadPolicyFromAdapter(Model $baseModel): ?Model
393
    {
394
        $newModel = clone $baseModel;
1,240✔
395
        $newModel->clearPolicy();
1,240✔
396

397
        try {
398
            $this->adapter?->loadPolicy($newModel);
1,240✔
399
            $newModel->sortPoliciesBySubjectHierarchy();
1,150✔
400
            $newModel->sortPoliciesByPriority();
1,150✔
401
        } catch (InvalidFilePathException) {
110✔
402
            return null;
100✔
403
        } catch (\Throwable $e) {
10✔
404
            throw $e;
10✔
405
        }
406

407
        return $newModel;
1,150✔
408
    }
409

410
    /**
411
     * Applies a modified model to the current enforcer.
412
     *
413
     * @param Model $newModel
414
     */
415
    public function applyModifiedModel(Model $newModel): void
416
    {
417
        $ok = false;
1,150✔
418
        $needToRebuild = false;
1,150✔
419

420
        try {
421
            if ($this->autoBuildRoleLinks) {
1,150✔
422
                $needToRebuild = true;
1,150✔
423

424
                $this->rebuildRoleLinks($newModel);
1,150✔
425
                $this->rebuildConditionalRoleLinks($newModel);
1,150✔
426
            }
427
            $this->model = $newModel;
1,150✔
428
            $ok = true;
1,150✔
429
        } finally {
430
            if (!$ok) {
1,150✔
431
                if ($this->autoBuildRoleLinks && $needToRebuild) {
×
432
                    $this->buildRoleLinks();
1,150✔
433
                }
434
            }
435
        }
436
    }
437

438
    /**
439
     * Rebuilds the role inheritance relations based on the new model.
440
     *
441
     * @param Model $newModel
442
     */
443
    public function rebuildRoleLinks(Model $newModel): void
444
    {
445
        if (count($this->rmMap) !== 0) {
1,150✔
446
            foreach ($this->rmMap as $rm) {
810✔
447
                $rm->clear();
810✔
448
            }
449

450
            $newModel->buildRoleLinks($this->rmMap);
810✔
451
        }
452
    }
453

454
    /**
455
     * Rebuilds the conditional role inheritance relations based on the new model.
456
     *
457
     * @param Model $newModel
458
     */
459
    public function rebuildConditionalRoleLinks(Model $newModel): void
460
    {
461
        if (!empty($this->condRmMap)) {
1,150✔
462
            foreach ($this->condRmMap as $rm) {
30✔
463
                $rm->clear();
30✔
464
            }
465

466
            $newModel->buildConditionalRoleLinks($this->condRmMap);
30✔
467
        }
468
    }
469

470
    /**
471
     * Reloads a filtered policy from file/database.
472
     *
473
     * @param mixed $filter
474
     *
475
     * @throws CasbinException
476
     */
477
    public function _loadFilteredPolicy($filter): void
478
    {
479
        if ($this->adapter instanceof FilteredAdapter) {
20✔
480
            $filteredAdapter = $this->adapter;
20✔
481
            $filteredAdapter->loadFilteredPolicy($this->model, $filter);
20✔
482
        } else {
483
            throw new CasbinException('filtered policies are not supported by this adapter');
×
484
        }
485

486
        $this->model->sortPoliciesBySubjectHierarchy();
20✔
487
        $this->model->sortPoliciesByPriority();
20✔
488
        $this->initRmMap();
20✔
489
        $this->model->printPolicy();
20✔
490
        if ($this->autoBuildRoleLinks) {
20✔
491
            $this->buildRoleLinks();
20✔
492
        }
493
    }
494

495
    /**
496
     * Reloads a filtered policy from file/database.
497
     *
498
     * @param mixed $filter
499
     *
500
     * @throws CasbinException
501
     */
502
    public function loadFilteredPolicy($filter): void
503
    {
504
        $this->model->clearPolicy();
20✔
505

506
        $this->_loadFilteredPolicy($filter);
20✔
507
    }
508

509
    /**
510
     * LoadIncrementalFilteredPolicy append a filtered policy from file/database.
511
     *
512
     * @param mixed $filter
513
     * @return void
514
     */
515
    public function loadIncrementalFilteredPolicy($filter): void
516
    {
517
        $this->_loadFilteredPolicy($filter);
10✔
518
    }
519

520
    /**
521
     * Returns true if the loaded policy has been filtered.
522
     *
523
     * @return bool
524
     */
525
    public function isFiltered(): bool
526
    {
527
        if (!$this->adapter instanceof FilteredAdapter) {
20✔
528
            return false;
10✔
529
        }
530

531
        $filteredAdapter = $this->adapter;
10✔
532

533
        return $filteredAdapter->isFiltered();
10✔
534
    }
535

536
    /**
537
     * Saves the current policy (usually after changed with Casbin API) back to file/database.
538
     *
539
     * @throws CasbinException
540
     */
541
    public function savePolicy(): void
542
    {
543
        if ($this->isFiltered()) {
20✔
544
            throw new CasbinException('cannot save a filtered policy');
10✔
545
        }
546

547
        $this->adapter?->savePolicy($this->model);
10✔
548

549
        if ($this->autoNotifyWatcher) {
10✔
550
            if ($this->watcher instanceof WatcherEx) {
10✔
551
                $this->watcher->updateForSavePolicy($this->model);
×
552
            } else {
553
                $this->watcher?->update();
10✔
554
            }
555
        }
556
    }
557

558
    /**
559
     * initRmMap initializes rmMap.
560
     *
561
     * @return void
562
     */
563
    public function initRmMap(): void
564
    {
565
        if (isset($this->model['g'])) {
1,260✔
566
            foreach ($this->model['g'] as $ptype => $value) {
870✔
567
                if (isset($this->rmMap[$ptype])) {
870✔
568
                    $rm = $this->rmMap[$ptype];
20✔
569
                    $rm->clear();
20✔
570
                    continue;
20✔
571
                }
572

573
                $tokensCount = count($value->tokens);
870✔
574
                $paramsTokensCount = count($value->paramsTokens);
870✔
575
                if ($tokensCount <= 2) {
870✔
576
                    if ($paramsTokensCount === 0) {
660✔
577
                        $value->rm = new DefaultRoleManager(10);
640✔
578
                        $this->rmMap[$ptype] = $value->rm;
640✔
579
                    } else {
580
                        $value->condRm = new DefaultConditionalRoleManager(10);
20✔
581
                        $this->condRmMap[$ptype] = $value->condRm;
20✔
582
                    }
583
                }
584
                if ($tokensCount > 2) {
870✔
585
                    if ($paramsTokensCount === 0) {
300✔
586
                        $value->rm = new DefaultDomainManager(10);
270✔
587
                        $this->rmMap[$ptype] = $value->rm;
270✔
588
                    } else {
589
                        $value->condRm = new DefaultConditionalDomainManager(10);
30✔
590
                        $this->condRmMap[$ptype] = $value->condRm;
30✔
591
                    }
592
                    $matchFunc = 'keyMatch(r_dom, p_dom)';
300✔
593
                    if (isset($this->model['m']['m']) && str_contains($this->model['m']['m']->value, $matchFunc)) {
300✔
594
                        $this->addNamedDomainMatchingFunc('g', 'keyMatch', fn(string $key1, string $key2) => BuiltinOperations::keyMatch($key1, $key2));
20✔
595
                    }
596
                }
597
            }
598
        }
599
    }
600

601
    /**
602
     * Changes the enforcing state of Casbin, when Casbin is disabled, all access will be allowed by the Enforce() function.
603
     *
604
     * @param bool $enabled
605
     */
606
    public function enableEnforce(bool $enabled = true): void
607
    {
608
        $this->enabled = $enabled;
10✔
609
    }
610

611
    /**
612
     * Changes whether Casbin will log messages to the Logger.
613
     *
614
     * @param bool $enabled
615
     */
616
    public function enableLog(bool $enabled = true): void
617
    {
618
        $this->logger->enableLog($enabled);
20✔
619
    }
620

621
    /**
622
     * Controls whether to save a policy rule automatically notify the Watcher when it is added or removed.
623
     *
624
     * @param bool $enabled
625
     */
626
    public function enableAutoNotifyWatcher(bool $enabled = true): void
627
    {
628
        $this->autoNotifyWatcher = $enabled;
×
629
    }
630

631
    /**
632
     * Controls whether to save a policy rule automatically to the adapter when it is added or removed.
633
     *
634
     * @param bool $autoSave
635
     */
636
    public function enableAutoSave(bool $autoSave = true): void
637
    {
638
        $this->autoSave = $autoSave;
10✔
639
    }
640

641
    /**
642
     * Controls whether to rebuild the role inheritance relations when a role is added or deleted.
643
     *
644
     * @param bool $autoBuildRoleLinks
645
     */
646
    public function enableAutoBuildRoleLinks(bool $autoBuildRoleLinks = true): void
647
    {
648
        $this->autoBuildRoleLinks = $autoBuildRoleLinks;
×
649
    }
650

651
    /**
652
     * Manually rebuild the role inheritance relations.
653
     */
654
    public function buildRoleLinks(): void
655
    {
656
        foreach ($this->rmMap as $rm) {
140✔
657
            $rm->clear();
140✔
658
        }
659

660
        $this->model->buildRoleLinks($this->rmMap);
140✔
661
    }
662

663
    /**
664
     * Use a custom matcher to decides whether a "subject" can access a "object" with the operation "action",
665
     * input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
666
     *
667
     * @param string $matcher
668
     * @param array $explains
669
     * @param mixed ...$rvals
670
     *
671
     * @return bool
672
     *
673
     * @throws CasbinException
674
     */
675
    protected function enforcing(string $matcher, &$explains = [], ...$rvals): bool
676
    {
677
        if (!$this->enabled) {
740✔
678
            return true;
10✔
679
        }
680

681
        $functions = $this->fm->getFunctions();
740✔
682

683
        if (isset($this->model['g'])) {
740✔
684
            foreach ($this->model['g'] as $key => $ast) {
370✔
685
                if (!is_null($ast->rm)) {
370✔
686
                    $functions[$key] = BuiltinOperations::generateGFunction($ast->rm);
340✔
687
                }
688
                if (!is_null($ast->condRm)) {
370✔
689
                    $functions[$key] = BuiltinOperations::generateConditionalGFunction($ast->condRm);
40✔
690
                }
691
            }
692
        }
693

694
        if (!isset($this->model['m']['m'])) {
740✔
695
            throw new CasbinException('model is undefined');
×
696
        }
697

698
        $rType = "r";
740✔
699
        $pType = "p";
740✔
700
        $eType = "e";
740✔
701
        $mType = "m";
740✔
702

703
        switch (true) {
704
            case $rvals[0] instanceof EnforceContext:
740✔
705
                $enforceContext = $rvals[0];
10✔
706
                $rType = $enforceContext->rType;
10✔
707
                $pType = $enforceContext->pType;
10✔
708
                $eType = $enforceContext->eType;
10✔
709
                $mType = $enforceContext->mType;
10✔
710
                array_shift($rvals);
10✔
711
                break;
10✔
712
            default:
713
                break;
740✔
714
        }
715

716
        $expString = '' === $matcher ? $this->model['m'][$mType]->value : Util::removeComments(Util::escapeAssertion($matcher));
740✔
717

718
        if (!isset($this->model['r'][$rType])){
740✔
NEW
719
            throw new CasbinException(sprintf('rType[%s] not defined', $rType));
×
720
        }
721

722
        if (!isset($this->model['p'][$pType])){
740✔
NEW
723
            throw new CasbinException(sprintf('pType[%s] not defined', $pType));
×
724
        }
725

726
        if (!isset($this->model['e'][$eType])){
740✔
NEW
727
            throw new CasbinException(sprintf('eType[%s] not defined', $eType));
×
728
        }
729

730
        $rTokens = array_values($this->model['r'][$rType]->tokens);
740✔
731
        $pTokens = array_values($this->model['p'][$pType]->tokens);
740✔
732

733
        if (count($rTokens) != count($rvals)) {
740✔
NEW
734
            throw new CasbinException(sprintf('invalid request size: expected %d, got %d, rvals: %s', count($rTokens), count($rvals), json_encode($rvals)));
×
735
        }
736
        $rParameters = array_combine($rTokens, $rvals);
740✔
737

738
        $parameters = [];
740✔
739
        $hasEval = Util::hasEval($expString);
740✔
740
        if ($hasEval) {
740✔
741
            $functions['eval'] = function (string $exp) use ($functions, &$parameters) {
30✔
742
                return $this->getExpressionLanguage($functions)->evaluate(Util::escapeAssertion($exp), $parameters);
20✔
743
            };
30✔
744
        }
745

746
        $expressionLanguage = $this->getExpressionLanguage($functions);
740✔
747
        $expression = $expressionLanguage->parse($expString, array_merge($rTokens, $pTokens));
740✔
748

749
        $policyEffects = [];
740✔
750
        $matcherResults = [];
740✔
751

752
        $effect = 0;
740✔
753
        $explainIndex = 0;
740✔
754

755
        $policyLen = count($this->model['p'][$pType]->policy);
740✔
756
        if (0 != $policyLen && str_contains($expString, $pType . '_')) {
740✔
757
            foreach ($this->model['p'][$pType]->policy as $policyIndex => $pvals) {
680✔
758
                if (count($pTokens) != count($pvals)) {
680✔
759
                    throw new CasbinException(sprintf("invalid policy size: expected %d, got %d, pvals: %s", count($pTokens), count($pvals), json_encode($pvals)));
×
760
                }
761

762
                $parameters = array_merge($rParameters, array_combine($pTokens, $pvals));
680✔
763
                $result = $expressionLanguage->evaluate($expression, $parameters);
680✔
764

765
                // set to no-match at first
766
                $matcherResults[$policyIndex] = 0;
680✔
767
                if (is_bool($result)) {
680✔
768
                    if ($result) {
680✔
769
                        $matcherResults[$policyIndex] = 1;
668✔
770
                    }
771
                } elseif (is_float($result)) {
×
772
                    if ($result != 0) {
×
773
                        $matcherResults[$policyIndex] = 1;
×
774
                    }
775
                } else {
776
                    throw new CasbinException('matcher result should be bool, int or float');
×
777
                }
778
                if (isset($parameters[$pType . '_eft'])) {
680✔
779
                    $eft = $parameters[$pType . '_eft'];
130✔
780
                    if ('allow' == $eft) {
130✔
781
                        $policyEffects[$policyIndex] = Effector::ALLOW;
110✔
782
                    } elseif ('deny' == $eft) {
110✔
783
                        $policyEffects[$policyIndex] = Effector::DENY;
90✔
784
                    } else {
785
                        $policyEffects[$policyIndex] = Effector::INDETERMINATE;
64✔
786
                    }
787
                } else {
788
                    $policyEffects[$policyIndex] = Effector::ALLOW;
560✔
789
                }
790

791
                [$effect, $explainIndex] = $this->eft->mergeEffects($this->model['e'][$eType]->value, $policyEffects, $matcherResults, $policyIndex, $policyLen);
680✔
792
                if ($effect != Effector::INDETERMINATE) {
680✔
793
                    break;
640✔
794
                }
795
            }
796
        } else {
797
            if ($hasEval) {
90✔
798
                throw new EvalFunctionException("please make sure rule exists in policy when using eval() in matcher");
10✔
799
            }
800

801
            $matcherResults[0] = 1;
80✔
802

803
            $parameters = $rParameters;
80✔
804
            foreach ($this->model['p'][$pType]->tokens as $token) {
80✔
805
                $parameters[$token] = '';
80✔
806
            }
807

808
            $result = $expressionLanguage->evaluate($expression, $parameters);
80✔
809

810
            if ($result) {
80✔
811
                $policyEffects[0] = Effector::ALLOW;
30✔
812
            } else {
813
                $policyEffects[0] = Effector::INDETERMINATE;
80✔
814
            }
815

816
            [$effect, $explainIndex] = $this->eft->mergeEffects($this->model['e'][$eType]->value, $policyEffects, $matcherResults, 0, 1);
80✔
817
        }
818

819
        if ($explains !== null) {
730✔
820
            if (($explainIndex != -1) && (count($this->model['p'][$pType]->policy) > $explainIndex)) {
730✔
821
                $explains = $this->model['p'][$pType]->policy[$explainIndex];
640✔
822
            }
823
        }
824

825
        $result = $effect == Effector::ALLOW;
730✔
826

827
        $this->logger->logEnforce($matcher, $rvals, $result, $explains);
730✔
828

829
        return $result;
730✔
830
    }
831

832
    /**
833
     * @param array $functions
834
     *
835
     * @return ExpressionLanguage
836
     */
837
    protected function getExpressionLanguage(array $functions): ExpressionLanguage
838
    {
839
        $expressionLanguage = new ExpressionLanguage();
740✔
840
        foreach ($functions as $key => $func) {
740✔
841
            $expressionLanguage->register(
740✔
842
                $key,
740✔
843
                static fn(...$args): string => sprintf($key . '(%1$s)', implode(',', $args)),
740✔
844
                static fn($arguments, ...$args) => $func(...$args)
740✔
845
            );
740✔
846
        }
847

848
        return $expressionLanguage;
740✔
849
    }
850

851
    /**
852
     * @param string $expString
853
     *
854
     * @return string|null
855
     */
856
    protected function getExpString(string $expString): string|null
857
    {
858
        return preg_replace_callback(
×
859
            '/([\s\S]*in\s+)\(([\s\S]+)\)([\s\S]*)/',
×
860
            static fn($m): string => $m[1] . '[' . $m[2] . ']' . $m[3],
×
861
            $expString
×
862
        );
×
863
    }
864

865
    /**
866
     * Decides whether a "subject" can access a "object" with the operation "action", input parameters are usually: (sub, obj, act).
867
     *
868
     * @param mixed ...$rvals
869
     *
870
     * @return bool
871
     *
872
     * @throws CasbinException
873
     */
874
    public function enforce(...$rvals): bool
875
    {
876
        $explains = [];
580✔
877
        return $this->enforcing('', $explains, ...$rvals);
580✔
878
    }
879

880
    /**
881
     * Use a custom matcher to decides whether a "subject" can access a "object" with the operation "action",
882
     * input parameters are usually: (matcher, sub, obj, act), use model matcher by default when matcher is "".
883
     *
884
     * @param string $matcher
885
     * @param mixed ...$rvals
886
     *
887
     * @return bool
888
     *
889
     * @throws CasbinException
890
     */
891
    public function enforceWithMatcher(string $matcher, ...$rvals): bool
892
    {
893
        $explains = [];
×
894
        return $this->enforcing($matcher, $explains, ...$rvals);
×
895
    }
896

897
    /**
898
     * EnforceEx explain enforcement by informing matched rules
899
     *
900
     * @param mixed ...$rvals
901
     * @return array
902
     */
903
    public function enforceEx(...$rvals)
904
    {
905
        $explain = [];
160✔
906
        $result = $this->enforcing("", $explain, ...$rvals);
160✔
907
        return [$result, $explain];
160✔
908
    }
909

910
    /**
911
     * BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
912
     *
913
     * @param integer $op policy operations.
914
     * @param string $ptype policy type.
915
     * @param string[][] $rules the rules.
916
     * @return void
917
     */
918
    public function buildIncrementalRoleLinks(int $op, string $ptype, array $rules): void
919
    {
920
        $this->model->buildIncrementalRoleLinks($this->rmMap, $op, "g", $ptype, $rules);
140✔
921
    }
922

923
    /**
924
     * BuildIncrementalConditionalRoleLinks provides incremental build the conditional role inheritance relations.
925
     *
926
     * @param integer $op policy operations.
927
     * @param string $ptype policy type.
928
     * @param string[][] $rules the rules.
929
     * @return void
930
     */
931
    public function buildIncrementalConditionalRoleLinks(int $op, string $ptype, array $rules): void
932
    {
933
        $this->model->buildIncrementalConditionalRoleLinks($this->condRmMap, $op, "g", $ptype, $rules);
30✔
934
    }
935

936
    /**
937
     * BatchEnforce enforce in batches
938
     *
939
     * @param string[][] $requests
940
     * @return bool[]
941
     */
942
    public function batchEnforce(array $requests): array
943
    {
944
        return array_map(fn(array $request) => $this->enforce(...$request), $requests);
10✔
945
    }
946

947
    /**
948
     * BatchEnforceWithMatcher enforce with matcher in batches
949
     *
950
     * @param string $matcher
951
     * @param string[][] $requests
952
     * @return bool[]
953
     */
954
    public function batchEnforceWithMatcher(string $matcher, array $requests): array
955
    {
956
        return array_map(fn(array $request) => $this->enforceWithMatcher($matcher, ...$request), $requests);
×
957
    }
958

959
    /**
960
     * AddNamedMatchingFunc add MatchingFunc by ptype RoleManager
961
     *
962
     * @param string $ptype
963
     * @param string $name
964
     * @param Closure $fn
965
     * @return boolean
966
     */
967
    public function addNamedMatchingFunc(string $ptype, string $name, Closure $fn): bool
968
    {
969
        if (isset($this->rmMap[$ptype])) {
40✔
970
            $rm = &$this->rmMap[$ptype];
40✔
971
            $rm->addMatchingFunc($name, $fn);
40✔
972
            return true;
40✔
973
        }
974
        return false;
×
975
    }
976

977
    /**
978
     * AddNamedDomainMatchingFunc add MatchingFunc by ptype to RoleManager
979
     *
980
     * @param string $ptype
981
     * @param string $name
982
     * @param Closure $fn
983
     * @return boolean
984
     */
985
    public function addNamedDomainMatchingFunc(string $ptype, string $name, Closure $fn): bool
986
    {
987
        if (isset($this->rmMap[$ptype])) {
40✔
988
            $rm = &$this->rmMap[$ptype];
40✔
989
            $rm->addDomainMatchingFunc($name, $fn);
40✔
990
            return true;
40✔
991
        }
992
        return false;
×
993
    }
994

995
    /**
996
     * AddNamedLinkConditionFunc Add condition function fn for Link userName->roleName,
997
     * when fn returns true, Link is valid, otherwise invalid.
998
     *
999
     * @param string $ptype
1000
     * @param string $user
1001
     * @param string $role
1002
     * @param Closure $fn
1003
     * @return boolean
1004
     */
1005
    public function addNamedLinkConditionFunc(string $ptype, string $user, string $role, Closure $fn): bool
1006
    {
1007
        if (isset($this->condRmMap[$ptype])) {
20✔
1008
            $rm = &$this->condRmMap[$ptype];
20✔
1009
            $rm->addLinkConditionFunc($user, $role, $fn);
20✔
1010
            return true;
20✔
1011
        }
1012
        return false;
×
1013
    }
1014

1015
    /**
1016
     * AddNamedDomainLinkConditionFunc Add condition function fn for Link userName-> {roleName, domain},
1017
     * when fn returns true, Link is valid, otherwise invalid.
1018
     *
1019
     * @param string $ptype
1020
     * @param string $user
1021
     * @param string $role
1022
     * @param string $domain
1023
     * @param Closure $fn
1024
     *
1025
     * @return boolean
1026
     */
1027
    public function addNamedDomainLinkConditionFunc(string $ptype, string $user, string $role, string $domain, Closure $fn): bool
1028
    {
1029
        if (isset($this->condRmMap[$ptype])) {
30✔
1030
            $rm = &$this->condRmMap[$ptype];
30✔
1031
            $rm->addDomainLinkConditionFunc($user, $role, $domain, $fn);
30✔
1032
            return true;
30✔
1033
        }
1034
        return false;
×
1035
    }
1036

1037
    /**
1038
     * SetNamedLinkConditionFuncParams Sets the parameters of the condition function fn for Link userName->roleName.
1039
     *
1040
     * @param string $ptype
1041
     * @param string $user
1042
     * @param string $role
1043
     * @param string ...$params
1044
     *
1045
     * @return boolean
1046
     */
1047
    public function setNamedLinkConditionFuncParams(string $ptype, string $user, string $role, string ...$params): bool
1048
    {
1049
        if (isset($this->condRmMap[$ptype])) {
10✔
1050
            $rm = &$this->condRmMap[$ptype];
10✔
1051
            $rm->setLinkConditionFuncParams($user, $role, ...$params);
10✔
1052
            return true;
10✔
1053
        }
1054
        return false;
×
1055
    }
1056

1057
    /**
1058
     * SetNamedDomainLinkConditionFuncParams Sets the parameters of the condition function fn
1059
     * for Link userName->{roleName, domain}.
1060
     *
1061
     * @param string $ptype
1062
     * @param string $user
1063
     * @param string $role
1064
     * @param string $domain
1065
     * @param string ...$params
1066
     *
1067
     * @return boolean
1068
     */
1069
    public function setNamedDomainLinkConditionFuncParams(string $ptype, string $user, string $role, string $domain, string ...$params): bool
1070
    {
1071
        if (isset($this->condRmMap[$ptype])) {
10✔
1072
            $rm = &$this->condRmMap[$ptype];
10✔
1073
            $rm->setDomainLinkConditionFuncParams($user, $role, $domain, ...$params);
10✔
1074
            return true;
10✔
1075
        }
1076
        return false;
×
1077
    }
1078
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc