• 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

95.65
/src/Model/Policy.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Casbin\Model;
6

7
use ArrayAccess;
8
use Casbin\Constant\Constants;
9
use Casbin\Exceptions\CasbinException;
10
use Casbin\Log\Logger;
11
use Casbin\Rbac\{ConditionalRoleManager, RoleManager};
12
use Casbin\Util\Util;
13

14
/**
15
 * Class Policy.
16
 *
17
 * @package Casbin\Model
18
 * @implements ArrayAccess<string, array<string, Assertion>>
19
 * @author techlee@qq.com
20
 */
21
abstract class Policy implements ArrayAccess
22
{
23
    public const POLICY_ADD = 0;
24

25
    public const POLICY_REMOVE = 1;
26

27
    const DEFAULT_SEP = ",";
28

29
    /**
30
     * All of the Model items.
31
     *
32
     * @var array<string, array<string, Assertion>>
33
     */
34
    protected array $items = [];
35

36
    /**
37
     * $logger.
38
     *
39
     * @var Logger|null
40
     */
41
    protected ?Logger $logger = null;
42

43
    /**
44
     * BuildIncrementalRoleLinks provides incremental build the role inheritance relations.
45
     *
46
     * @param RoleManager[] $rmMap
47
     * @param integer $op
48
     * @param string $sec
49
     * @param string $ptype
50
     * @param string[][] $rules
51
     * @return void
52
     */
53
    public function buildIncrementalRoleLinks(array $rmMap, int $op, string $sec, string $ptype, array $rules): void
54
    {
55
        if ($sec == "g" && isset($rmMap[$ptype]) && isset($this->items[$sec][$ptype])) {
140✔
56
            $this->items[$sec][$ptype]->buildIncrementalRoleLinks($rmMap[$ptype], $op, $rules);
130✔
57
        }
58
    }
59

60
    /**
61
     * Initializes the roles in RBAC.
62
     *
63
     * @param RoleManager[] $rmMap
64
     * @throws CasbinException
65
     */
66
    public function buildRoleLinks(array $rmMap): void
67
    {
68
        $this->printPolicy();
810✔
69
        if (!isset($this->items['g'])) {
810✔
70
            return;
×
71
        }
72

73
        foreach ($this->items['g'] as $ptype => $ast) {
810✔
74
            if (isset($rmMap[$ptype])) {
810✔
75
                $rm = $rmMap[$ptype];
810✔
76
                $ast->buildRoleLinks($rm);
810✔
77
            }
78
        }
79
    }
80

81
    /**
82
     * BuildIncrementalConditionalRoleLinks provides incremental build the role inheritance relations.
83
     *
84
     * @param ConditionalRoleManager[] $condRmMap
85
     * @param integer $op
86
     * @param string $sec
87
     * @param string $ptype
88
     * @param string[][] $rules
89
     * @return void
90
     */
91
    public function buildIncrementalConditionalRoleLinks(array $condRmMap, int $op, string $sec, string $ptype, array $rules): void
92
    {
93
        if ($sec == "g" && isset($condRmMap[$ptype]) && isset($this->items[$sec][$ptype])) {
30✔
94
            $this->items[$sec][$ptype]->buildIncrementalConditionalRoleLinks($condRmMap[$ptype], $op, $rules);
10✔
95
        }
96
    }
97

98
    /**
99
     * Initializes the roles in RBAC with conditions.
100
     *
101
     * @param ConditionalRoleManager[] $condRmMap
102
     * @throws CasbinException
103
     */
104
    public function buildConditionalRoleLinks(array $condRmMap): void
105
    {
106
        $this->printPolicy();
30✔
107
        if (!isset($this->items['g'])) {
30✔
108
            return;
×
109
        }
110

111
        foreach ($this->items['g'] as $ptype => $ast) {
30✔
112
            if (isset($condRmMap[$ptype])) {
30✔
113
                $rm = $condRmMap[$ptype];
30✔
114
                $ast->buildConditionalRoleLinks($rm);
30✔
115
            }
116
        }
117
    }
118

119
    /**
120
     * Prints the policy to log.
121
     */
122
    public function printPolicy(): void
123
    {
124
        if (!$this->getLogger()?->isEnabled()) {
830✔
125
            return;
830✔
126
        }
127

128
        $policy = [];
10✔
129
        foreach (['p', 'g'] as $sec) {
10✔
130
            if (!isset($this->items[$sec])) {
10✔
131
                continue;
×
132
            }
133

134
            foreach ($this->items[$sec] as $ptype => $ast) {
10✔
135
                $policy[$ptype] = array_merge(
10✔
136
                    $policy[$ptype] ?? [],
10✔
137
                    $ast->policy
10✔
138
                );
10✔
139
            }
140
        }
141

142
        $this->getLogger()->logPolicy($policy);
10✔
143
    }
144

145
    /**
146
     * Clears all current policy.
147
     */
148
    public function clearPolicy(): void
149
    {
150
        foreach (['p', 'g'] as $sec) {
1,250✔
151
            if (!isset($this->items[$sec])) {
1,250✔
152
                return;
410✔
153
            }
154

155
            foreach ($this->items[$sec] as $key => $ast) {
1,250✔
156
                $this->items[$sec][$key]->policy = [];
1,250✔
157
                $this->items[$sec][$key]->policyMap = [];
1,250✔
158
            }
159
        }
160
    }
161

162
    /**
163
     * Gets all rules in a policy.
164
     *
165
     * @param string $sec
166
     * @param string $ptype
167
     *
168
     * @return string[][]
169
     */
170
    public function getPolicy(string $sec, string $ptype): array
171
    {
172
        return $this->items[$sec][$ptype]->policy;
80✔
173
    }
174

175
    /**
176
     * Gets rules based on field filters from a policy.
177
     *
178
     * @param string $sec
179
     * @param string $ptype
180
     * @param int $fieldIndex
181
     * @param string ...$fieldValues
182
     *
183
     * @return string[][]
184
     */
185
    public function getFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): array
186
    {
187
        $res = [];
70✔
188

189
        foreach ($this->items[$sec][$ptype]->policy as $rule) {
70✔
190
            $matched = true;
70✔
191
            foreach ($fieldValues as $i => $fieldValue) {
70✔
192
                if ('' != $fieldValue && $rule[$fieldIndex + intval($i)] != $fieldValue) {
70✔
193
                    $matched = false;
70✔
194

195
                    break;
70✔
196
                }
197
            }
198

199
            if ($matched) {
70✔
200
                $res[] = $rule;
70✔
201
            }
202
        }
203

204
        return $res;
70✔
205
    }
206

207
    /**
208
     * Determines whether a model has the specified policy rule.
209
     *
210
     * @param string $sec
211
     * @param string $ptype
212
     * @param string[] $rule
213
     *
214
     * @return bool
215
     */
216
    public function hasPolicy(string $sec, string $ptype, array $rule): bool
217
    {
218
        if (!isset($this->items[$sec][$ptype])) {
390✔
219
            return false;
×
220
        }
221

222
        return isset($this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $rule)]);
390✔
223
    }
224

225
    /**
226
     * Determines whether a model has any of the specified policies. If one is found we return true.
227
     *
228
     * @param string $sec
229
     * @param string $ptype
230
     * @param string[][] $rules
231
     *
232
     * @return bool
233
     */
234
    public function hasPolicies(string $sec, string $ptype, array $rules): bool
235
    {
236
        foreach ($rules as $rule) {
170✔
237
            if ($this->hasPolicy($sec, $ptype, $rule)) {
170✔
238
                return true;
80✔
239
            }
240
        }
241

242
        return false;
140✔
243
    }
244

245
    /**
246
     * Adds a policy rule to the model.
247
     *
248
     * @param string $sec
249
     * @param string $ptype
250
     * @param string[] $rule
251
     */
252
    public function addPolicy(string $sec, string $ptype, array $rule): void
253
    {
254
        $assertion = &$this->items[$sec][$ptype];
340✔
255
        $assertion->policy[] = $rule;
340✔
256
        $assertion->policyMap[implode(self::DEFAULT_SEP, $rule)] = count($this->items[$sec][$ptype]->policy) - 1;
340✔
257

258
        $hasPriority = isset($assertion->fieldIndexMap[Constants::PRIORITY_INDEX]);
340✔
259
        if ($sec == 'p' && $hasPriority) {
340✔
260
            $idxInsert = $rule[$assertion->fieldIndexMap[Constants::PRIORITY_INDEX]];
20✔
261
            for ($i = count($assertion->policy) - 1; $i > 0; $i--) {
20✔
262
                $idx = $assertion->policy[$i - 1][$assertion->fieldIndexMap[Constants::PRIORITY_INDEX]];
20✔
263
                if ($idx > $idxInsert) {
20✔
264
                    $assertion->policy[$i] = $assertion->policy[$i - 1];
20✔
265
                    $assertion->policyMap[implode(self::DEFAULT_SEP, $assertion->policy[$i - 1])]++;
20✔
266
                } else {
267
                    break;
20✔
268
                }
269
            }
270
            $assertion->policy[$i] = $rule;
20✔
271
            $assertion->policyMap[implode(self::DEFAULT_SEP, $rule)] = $i;
20✔
272
        }
273
    }
274

275
    /**
276
     * Adds a policy rules to the model.
277
     *
278
     * @param string $sec
279
     * @param string $ptype
280
     * @param string[][] $rules
281
     */
282
    public function addPolicies(string $sec, string $ptype, array $rules): void
283
    {
284
        $this->addPoliciesWithAffected($sec, $ptype, $rules);
130✔
285
    }
286

287
    /**
288
     * Adds policy rules to the model, and returns affected rules.
289
     * 
290
     * @param string $sec
291
     * @param string $ptype
292
     * @param string[][] $rules
293
     * 
294
     * @return string[][]
295
     */
296
    public function addPoliciesWithAffected(string $sec, string $ptype, array $rules): array
297
    {
298
        $affected = [];
130✔
299

300
        foreach ($rules as $rule) {
130✔
301
            $hashKey = implode(self::DEFAULT_SEP, $rule);
130✔
302
            if (isset($this->items[$sec][$ptype]->policyMap[$hashKey])) {
130✔
303
                continue;
40✔
304
            }
305

306
            $affected[] = $rule;
130✔
307
            $this->addPolicy($sec, $ptype, $rule);
130✔
308
        }
309

310
        return $affected;
130✔
311
    }
312

313
    /**
314
     * Updates a policy rule from the model.
315
     *
316
     * @param string $sec
317
     * @param string $ptype
318
     * @param string[] $oldRule
319
     * @param string[] $newRule
320
     *
321
     * @return bool
322
     */
323
    public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newRule): bool
324
    {
325
        $oldPolicy = implode(self::DEFAULT_SEP, $oldRule);
20✔
326
        if (!isset($this->items[$sec][$ptype]->policyMap[$oldPolicy])) {
20✔
327
            return false;
×
328
        }
329

330
        $index = $this->items[$sec][$ptype]->policyMap[$oldPolicy];
20✔
331
        $this->items[$sec][$ptype]->policy[$index] = $newRule;
20✔
332
        unset($this->items[$sec][$ptype]->policyMap[$oldPolicy]);
20✔
333
        $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $newRule)] = $index;
20✔
334

335
        return true;
20✔
336
    }
337

338
    /**
339
     * UpdatePolicies updates a policy rule from the model.
340
     *
341
     * @param string $sec
342
     * @param string $ptype
343
     * @param string[][] $oldRules
344
     * @param string[][] $newRules
345
     * @return boolean
346
     */
347
    public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): bool
348
    {
349
        $modifiedRuleIndex = [];
20✔
350

351
        $newIndex = 0;
20✔
352
        foreach ($oldRules as $oldIndex => $oldRule) {
20✔
353
            $oldPolicy = implode(self::DEFAULT_SEP, $oldRule);
20✔
354
            $index = $this->items[$sec][$ptype]->policyMap[$oldPolicy] ?? null;
20✔
355
            if (is_null($index)) {
20✔
356
                // rollback
357
                foreach ($modifiedRuleIndex as $index => $oldNewIndex) {
20✔
358
                    $this->items[$sec][$ptype]->policy[$index] = $oldRules[$oldNewIndex[0]];
10✔
359
                    $oldPolicy = implode(self::DEFAULT_SEP, $oldRules[$oldNewIndex[0]]);
10✔
360
                    $newPolicy = implode(self::DEFAULT_SEP, $newRules[$oldNewIndex[1]]);
10✔
361
                    unset($this->items[$sec][$ptype]->policyMap[$newPolicy]);
10✔
362
                    $this->items[$sec][$ptype]->policyMap[$oldPolicy] = $index;
10✔
363
                }
364
                return false;
20✔
365
            }
366

367
            $this->items[$sec][$ptype]->policy[$index] = $newRules[$newIndex];
20✔
368
            unset($this->items[$sec][$ptype]->policyMap[$oldPolicy]);
20✔
369
            $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $newRules[$newIndex])] = $index;
20✔
370
            $modifiedRuleIndex[$index] = [$oldIndex, $newIndex];
20✔
371
            $newIndex++;
20✔
372
        }
373

374
        return true;
20✔
375
    }
376

377
    /**
378
     * Removes a policy rule from the model.
379
     *
380
     * @param string $sec
381
     * @param string $ptype
382
     * @param array $rule
383
     *
384
     * @return bool
385
     */
386
    public function removePolicy(string $sec, string $ptype, array $rule): bool
387
    {
388
        if (!isset($this->items[$sec][$ptype])) {
160✔
389
            return false;
×
390
        }
391

392
        $hashKey = implode(self::DEFAULT_SEP, $rule);
160✔
393
        if (!isset($this->items[$sec][$ptype]->policyMap[$hashKey])) {
160✔
394
            return false;
40✔
395
        }
396

397
        $index = $this->items[$sec][$ptype]->policyMap[$hashKey];
160✔
398
        array_splice($this->items[$sec][$ptype]->policy, $index, 1);
160✔
399

400
        unset($this->items[$sec][$ptype]->policyMap[$hashKey]);
160✔
401

402
        $count = count($this->items[$sec][$ptype]->policy);
160✔
403
        for ($i = $index; $i < $count; $i++) {
160✔
404
            $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $this->items[$sec][$ptype]->policy[$i])] = $i;
120✔
405
        }
406

407
        return true;
160✔
408
    }
409

410
    /**
411
     * Removes a policy rules from the model.
412
     *
413
     * @param string $sec
414
     * @param string $ptype
415
     * @param string[][] $rules
416
     *
417
     * @return bool
418
     */
419
    public function removePolicies(string $sec, string $ptype, array $rules): bool
420
    {
421
        if (!isset($this->items[$sec][$ptype])) {
80✔
422
            return false;
×
423
        }
424

425
        foreach ($rules as $rule) {
80✔
426
            $this->removePolicy($sec, $ptype, $rule);
80✔
427
        }
428

429
        return true;
80✔
430
    }
431

432
    /**
433
     * Removes policy rules based on field filters from the model.
434
     *
435
     * @param string $sec
436
     * @param string $ptype
437
     * @param int $fieldIndex
438
     * @param string ...$fieldValues
439
     *
440
     * If more than one rule is removed, return the removed rule array, otherwise return false
441
     * @return string[][]|false
442
     */
443
    public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues)
444
    {
445
        $tmp = [];
90✔
446
        $effects = [];
90✔
447
        $res = false;
90✔
448

449
        if (!isset($this->items[$sec][$ptype])) {
90✔
450
            return $res;
10✔
451
        }
452

453
        $this->items[$sec][$ptype]->policyMap = [];
90✔
454

455
        foreach ($this->items[$sec][$ptype]->policy as $index => $rule) {
90✔
456
            $matched = true;
90✔
457
            foreach ($fieldValues as $i => $fieldValue) {
90✔
458
                if ('' != $fieldValue && $rule[$fieldIndex + intval($i)] != $fieldValue) {
90✔
459
                    $matched = false;
70✔
460
                    break;
70✔
461
                }
462
            }
463

464
            if ($matched) {
90✔
465
                $effects[] = $rule;
90✔
466
            } else {
467
                $tmp[] = $rule;
70✔
468
                $this->items[$sec][$ptype]->policyMap[implode(self::DEFAULT_SEP, $rule)] = count($tmp) - 1;
70✔
469
            }
470
        }
471

472
        if (count($tmp) != count($this->items[$sec][$ptype]->policy)) {
90✔
473
            $this->items[$sec][$ptype]->policy = $tmp;
90✔
474
            $res = true;
90✔
475
        }
476

477
        return $res ? $effects : false;
90✔
478
    }
479

480
    /**
481
     * Gets all values for a field for all rules in a policy, duplicated values are removed.
482
     *
483
     * @param string $sec
484
     * @param string $ptype
485
     * @param int $fieldIndex
486
     *
487
     * @return string[]
488
     */
489
    public function getValuesForFieldInPolicy(string $sec, string $ptype, int $fieldIndex): array
490
    {
491
        $values = [];
90✔
492

493
        if (!isset($this->items[$sec][$ptype])) {
90✔
494
            return $values;
20✔
495
        }
496

497
        foreach ($this->items[$sec][$ptype]->policy as $rule) {
90✔
498
            $values[] = $rule[$fieldIndex];
90✔
499
        }
500

501
        Util::arrayRemoveDuplicates($values);
90✔
502

503
        return $values;
90✔
504
    }
505

506
    /**
507
     * Gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
508
     *
509
     * @param string $sec
510
     * @param int $fieldIndex
511
     *
512
     * @return string[]
513
     */
514
    public function getValuesForFieldInPolicyAllTypes(string $sec, int $fieldIndex): array
515
    {
516
        $values = [];
40✔
517

518
        foreach ($this->items[$sec] as $key => $ptype) {
40✔
519
            $values = array_merge($values, $this->getValuesForFieldInPolicy($sec, $key, $fieldIndex));
40✔
520
        }
521

522
        Util::arrayRemoveDuplicates($values);
40✔
523

524
        return $values;
40✔
525
    }
526

527
    /**
528
     * Gets all values for a field for all rules in a policy of all ptypes, duplicated values are removed.
529
     *
530
     * @param string $sec
531
     * @param string $field
532
     * 
533
     * @return array<string>
534
     * @throws CasbinException
535
     */
536
    public function getValuesForFieldInPolicyAllTypesByName(string $sec, string $field): array
537
    {
538
        $values = [];
30✔
539

540
        foreach ($this->items[$sec] as $ptype => $rules) {
30✔
541
            $index = $this->getFieldIndex($ptype, $field);
30✔
542
            $v = $this->getValuesForFieldInPolicy($sec, $ptype, $index);
30✔
543

544
            $values = array_merge($values, $v);
30✔
545
        }
546

547
        Util::arrayRemoveDuplicates($values);
30✔
548

549
        return $values;
30✔
550
    }
551

552
    /**
553
     * Gets the index for a given ptype and field.
554
     *
555
     * @param string $ptype
556
     * @param string $field
557
     * 
558
     * @return int $fieldIndex
559
     * @throws CasbinException
560
     */
561
    public function getFieldIndex(string $ptype, string $field): int
562
    {
563
        $assertion = &$this->items['p'][$ptype];
1,150✔
564
        if (isset($assertion->fieldIndexMap[$field])) {
1,150✔
565
            return $assertion->fieldIndexMap[$field];
130✔
566
        }
567

568
        $pattern = $ptype . '_' . $field;
1,150✔
569
        $index = -1;
1,150✔
570

571
        foreach ($assertion->tokens as $i => $token) {
1,150✔
572
            if ($token == $pattern) {
1,150✔
573
                $index = $i;
220✔
574
                break;
220✔
575
            }
576
        }
577

578
        if ($index == -1) {
1,150✔
579
            throw new CasbinException($field . ' index is not set, please use enforcer.SetFieldIndex() to set index');
1,140✔
580
        }
581

582
        $assertion->fieldIndexMap[$field] = $index;
220✔
583

584
        return $index;
220✔
585
    }
586

587
    /**
588
     * Sets the index for a given ptype and field.
589
     *
590
     * @param string $ptype
591
     * @param string $field
592
     * @param int $index
593
     */
594
    public function setFieldIndex(string $ptype, string $field, int $index): void
595
    {
596
        $assertion = &$this->items['p'][$ptype];
10✔
597
        $assertion->fieldIndexMap[$field] = $index;
10✔
598
    }
599

600
    /**
601
     * Sets the current logger.
602
     *
603
     * @param Logger $logger
604
     *
605
     * @return void
606
     */
607
    public function setLogger(Logger $logger): void
608
    {
609
        array_walk($this->items, function (array $astMap) use ($logger) {
1,370✔
610
            array_walk($astMap, fn(Assertion $ast) => $ast->setLogger($logger));
1,250✔
611
        });
1,370✔
612

613
        $this->logger = $logger;
1,370✔
614
    }
615

616
    /**
617
     * Returns the current logger.
618
     *
619
     * @return Logger|null
620
     */
621
    public function getLogger(): ?Logger
622
    {
623
        return $this->logger;
1,250✔
624
    }
625

626
    /**
627
     * Determine if the given Model option exists.
628
     *
629
     * @param string $offset
630
     *
631
     * @return bool
632
     */
633
    public function offsetExists($offset): bool
634
    {
635
        return isset($this->items[$offset]);
1,280✔
636
    }
637

638
    /**
639
     * Get a Model option.
640
     *
641
     * @param string $offset
642
     *
643
     * @return array<string, Assertion>|null
644
     */
645
    public function offsetGet($offset): ?array
646
    {
647
        return $this->items[$offset] ?? null;
1,270✔
648
    }
649

650
    /**
651
     * Set a Model option.
652
     *
653
     * @param string $offset
654
     * @param array<string, Assertion> $value
655
     */
656
    public function offsetSet($offset, $value): void
657
    {
658
        $this->items[$offset] = $value;
1,140✔
659
    }
660

661
    /**
662
     * Unset a Model option.
663
     *
664
     * @param string $offset
665
     */
666
    public function offsetUnset($offset): void
667
    {
668
        unset($this->items[$offset]);
×
669
    }
670
}
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

© 2026 Coveralls, Inc