• 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.24
/src/Util/BuiltinOperations.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Casbin\Util;
6

7
use Casbin\Rbac\{ConditionalRoleManager, RoleManager};
8
use Closure, DateTime, Exception;
9

10
/**
11
 * Class BuiltinOperations.
12
 *
13
 * @author techlee@qq.com
14
 */
15
class BuiltinOperations
16
{
17
    /**
18
     * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
19
     * For example, "/foo/bar" matches "/foo/*".
20
     *
21
     * @param string $key1
22
     * @param string $key2
23
     *
24
     * @return bool
25
     */
26
    public static function keyMatch(string $key1, string $key2): bool
27
    {
28
        if (!str_contains($key2, '*')) {
120✔
29
            return $key1 == $key2;
120✔
30
        }
31

32
        $needle = rtrim($key2, '*');
110✔
33

34
        return substr($key1, 0, \strlen($needle)) === (string)$needle;
110✔
35
    }
36

37
    /**
38
     * The wrapper for KeyMatch.
39
     *
40
     * @param mixed ...$args
41
     *
42
     * @return bool
43
     */
44
    public static function keyMatchFunc(...$args): bool
45
    {
46
        [$name1, $name2] = $args;
60✔
47

48
        return self::keyMatch($name1, $name2);
60✔
49
    }
50

51
    /**
52
     * KeyGet returns the matched part
53
     * For example, "/foo/bar/foo" matches "/foo/*"
54
     * "bar/foo" will been returned
55
     *
56
     * @param string $key1
57
     * @param string $key2
58
     * @return string
59
     */
60
    public static function keyGet(string $key1, string $key2): string
61
    {
62
        $i = strpos($key2, '*');
10✔
63
        if ($i === false) {
10✔
64
            return "";
10✔
65
        }
66
        if (strlen($key1) > $i) {
10✔
67
            if (substr($key1, 0, $i) == substr($key2, 0, $i)) {
10✔
68
                return substr($key1, $i);
10✔
69
            }
70
        }
71
        return '';
10✔
72
    }
73

74
    /**
75
     * KeyGetFunc is the wrapper for KeyGet
76
     *
77
     * @param mixed ...$args
78
     * @return string
79
     */
80
    public static function keyGetFunc(...$args)
81
    {
82
        [$name1, $name2] = $args;
10✔
83

84
        return self::keyGet($name1, $name2);
10✔
85
    }
86

87
    /**
88
     * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
89
     * For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/:resource".
90
     *
91
     * @param string $key1
92
     * @param string $key2
93
     *
94
     * @return bool
95
     */
96
    public static function keyMatch2(string $key1, string $key2): bool
97
    {
98
        if ('*' === $key2) {
100✔
99
            $key2 = '.*';
30✔
100
        }
101
        $key2 = str_replace(['/*'], ['/.*'], $key2);
100✔
102

103
        $pattern = '/:[^\/]+/';
100✔
104

105
        $key2 = preg_replace_callback($pattern, static fn ($m) => '[^\/]+', $key2);
100✔
106

107
        return self::regexMatch($key1, '^' . $key2 . '$');
100✔
108
    }
109

110
    /**
111
     * The wrapper for KeyMatch2.
112
     *
113
     * @param mixed ...$args
114
     *
115
     * @return bool
116
     */
117
    public static function keyMatch2Func(...$args): bool
118
    {
119
        [$name1, $name2] = $args;
30✔
120

121
        return self::keyMatch2($name1, $name2);
30✔
122
    }
123

124
    /**
125
     * KeyGet2 returns value matched pattern
126
     * For example, "/resource1" matches "/:resource"
127
     * if the pathVar == "resource", then "resource1" will be returned
128
     *
129
     * @param string $key1
130
     * @param string $key2
131
     * @param string $pathVar
132
     * @return string
133
     */
134
    public static function keyGet2(string $key1, string $key2, string $pathVar): string
135
    {
136
        $key2 = str_replace(['/*'], ['/.*'], $key2);
10✔
137

138
        $pattern = '/:[^\/]+/';
10✔
139
        $keys = [];
10✔
140
        preg_match_all($pattern, $key2, $keys);
10✔
141
        $keys = $keys[0];
10✔
142
        $key2 = preg_replace_callback($pattern, static fn ($m): string => '([^\/]+)', $key2);
10✔
143

144
        $key2 = "~^" . $key2 . "$~";
10✔
145
        $values = [];
10✔
146
        preg_match($key2, $key1, $values);
10✔
147

148
        if (count($values) === 0) {
10✔
149
            return '';
10✔
150
        }
151

152
        foreach ($keys as $i => $key) {
10✔
153
            if ($pathVar == substr($key, 1)) {
10✔
154
                return $values[$i + 1];
10✔
155
            }
156
        }
157

158
        return '';
10✔
159
    }
160

161
    /**
162
     * KeyGet2Func is the wrapper for KeyGet2
163
     *
164
     * @param mixed ...$args
165
     * @return string
166
     */
167
    public static function keyGet2Func(...$args)
168
    {
169
        [$name1, $name2, $key] = $args;
10✔
170

171
        return self::keyGet2($name1, $name2, $key);
10✔
172
    }
173

174
    /**
175
     * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
176
     * For example, "/foo/bar" matches "/foo/*", "/resource1" matches "/{resource}".
177
     *
178
     * @param string $key1
179
     * @param string $key2
180
     *
181
     * @return bool
182
     */
183
    public static function keyMatch3(string $key1, string $key2): bool
184
    {
185
        $key2 = str_replace(['/*'], ['/.*'], $key2);
20✔
186

187
        $pattern = '/\{[^\/]+\}/';
20✔
188
        $key2 = preg_replace_callback($pattern, static fn ($m): string => '[^\/]+', $key2);
20✔
189

190
        return self::regexMatch($key1, '^' . $key2 . '$');
20✔
191
    }
192

193
    /**
194
     * The wrapper for KeyMatch3.
195
     *
196
     * @param mixed ...$args
197
     *
198
     * @return bool
199
     */
200
    public static function keyMatch3Func(...$args): bool
201
    {
202
        [$name1, $name2] = $args;
10✔
203

204
        return self::keyMatch3($name1, $name2);
10✔
205
    }
206

207
    /**
208
     * Determines whether key1 matches the pattern of key2 (similar to RESTful path), key2 can contain a *.
209
     * Besides what KeyMatch3 does, KeyMatch4 can also match repeated patterns:
210
     * "/parent/123/child/123" matches "/parent/{id}/child/{id}"
211
     * "/parent/123/child/456" does not match "/parent/{id}/child/{id}"
212
     * But KeyMatch3 will match both.
213
     *
214
     * @param string $key1
215
     * @param string $key2
216
     *
217
     * @return bool
218
     */
219
    public static function keyMatch4(string $key1, string $key2): bool
220
    {
221
        $key2 = str_replace(['/*'], ['/.*'], $key2);
10✔
222

223
        $tokens = [];
10✔
224
        $pattern = '/\{([^\/]+)\}/';
10✔
225
        $key2 = preg_replace_callback(
10✔
226
            $pattern,
10✔
227
            function ($m) use (&$tokens) {
10✔
228
                $tokens[] = $m[1];
10✔
229
                return '([^\/]+)';
10✔
230
            },
10✔
231
            $key2
10✔
232
        );
10✔
233

234
        $matched = preg_match_all('~^' . $key2 . '$~', $key1, $matches);
10✔
235
        if (!boolval($matched)) {
10✔
236
            return false;
10✔
237
        }
238

239
        $values = [];
10✔
240
        foreach ($tokens as $key => $token) {
10✔
241
            if (!isset($values[$token])) {
10✔
242
                $values[$token] = $matches[$key + 1];
10✔
243
            }
244
            if ($values[$token] != $matches[$key + 1]) {
10✔
245
                return false;
10✔
246
            }
247
        }
248

249
        return true;
10✔
250
    }
251

252
    /**
253
     * The wrapper for KeyMatch4.
254
     *
255
     * @param mixed ...$args
256
     *
257
     * @return bool
258
     */
259
    public static function keyMatch4Func(...$args): bool
260
    {
261
        [$name1, $name2] = $args;
10✔
262

263
        return self::keyMatch4($name1, $name2);
10✔
264
    }
265

266
    /**
267
     * Determines whether key1 matches the pattern of key2 and ignores the parameters in key2.
268
     * For example, "/foo/bar?status=1&type=2" matches "/foo/bar"
269
     *
270
     * @param string $key1
271
     * @param string $key2
272
     *
273
     * @return bool
274
     */
275
    public static function keyMatch5(string $key1, string $key2): bool
276
    {
277
        $pos = strpos($key1, '?');
10✔
278
        if ($pos === false) {
10✔
279
            return $key1 == $key2;
10✔
280
        }
281

282
        return substr($key1, 0, $pos) == $key2;
10✔
283
    }
284

285
    /**
286
     * the wrapper for KeyMatch5.
287
     *
288
     * @param mixed ...$args
289
     *
290
     * @return bool
291
     */
292
    public static function keyMatch5Func(...$args): bool
293
    {
294
        [$name1, $name2] = $args;
10✔
295

296
        return self::keyMatch5($name1, $name2);
10✔
297
    }
298

299
    /**
300
     * Determines whether key1 matches the pattern of key2 in regular expression.
301
     *
302
     * @param string $key1
303
     * @param string $key2
304
     *
305
     * @return bool
306
     */
307
    public static function regexMatch(string $key1, string $key2): bool
308
    {
309
        return (bool)preg_match('~' . $key2 . '~', $key1);
190✔
310
    }
311

312
    /**
313
     * The wrapper for RegexMatch.
314
     *
315
     * @param mixed ...$args
316
     *
317
     * @return bool
318
     */
319
    public static function regexMatchFunc(...$args): bool
320
    {
321
        [$name1, $name2] = $args;
110✔
322

323
        return self::regexMatch($name1, $name2);
110✔
324
    }
325

326
    /**
327
     * Determines whether IP address ip1 matches the pattern of IP address ip2, ip2 can be an IP address or a CIDR
328
     * pattern.
329
     *
330
     * @param string $ip1
331
     * @param string $ip2
332
     *
333
     * @return bool
334
     *
335
     * @throws Exception
336
     */
337
    public static function ipMatch(string $ip1, string $ip2): bool
338
    {
339
        return Util::ipInSubnet($ip1, $ip2);
30✔
340
    }
341

342
    /**
343
     * The wrapper for IPMatch.
344
     *
345
     * @param mixed ...$args
346
     *
347
     * @return bool
348
     *
349
     * @throws Exception
350
     */
351
    public static function ipMatchFunc(...$args): bool
352
    {
353
        [$ip1, $ip2] = $args;
30✔
354

355
        return self::ipMatch($ip1, $ip2);
30✔
356
    }
357

358
    /**
359
     * Returns true if the specified `string` matches the given glob `pattern`.
360
     *
361
     * @param string $str
362
     * @param string $pattern
363
     *
364
     * @return bool
365
     *
366
     * @throws Exception
367
     */
368
    public static function globMatch(string $str, string $pattern): bool
369
    {
370
        return fnmatch($pattern, $str, FNM_PATHNAME | FNM_PERIOD);
10✔
371
    }
372

373
    /**
374
     * The wrapper for globMatch.
375
     *
376
     * @param mixed ...$args
377
     *
378
     * @return bool
379
     *
380
     * @throws Exception
381
     */
382
    public static function globMatchFunc(...$args): bool
383
    {
384
        $str = $args[0];
10✔
385
        $pattern = $args[1];
10✔
386

387
        return self::globMatch($str, $pattern);
10✔
388
    }
389

390
    /**
391
     * The factory method of the g(_, _) function.
392
     *
393
     * @param RoleManager|null $rm
394
     *
395
     * @return Closure
396
     */
397
    public static function generateGFunction(?RoleManager $rm = null): Closure
398
    {
399
        $memorized = [];
340✔
400
        return function (...$args) use ($rm, &$memorized) {
340✔
401
            $key = implode(chr(0b0), $args);
340✔
402

403
            if (isset($memorized[$key])) {
340✔
404
                return $memorized[$key];
210✔
405
            }
406

407
            [$name1, $name2] = $args;
340✔
408

409
            if (null === $rm) {
340✔
410
                $v = $name1 == $name2;
×
411
            } elseif (2 == count($args)) {
340✔
412
                $v = $rm->hasLink($name1, $name2);
280✔
413
            } else {
414
                $domain = (string)$args[2];
60✔
415
                $v = $rm->hasLink($name1, $name2, $domain);
60✔
416
            }
417

418
            $memorized[$key] = $v;
340✔
419
            return $v;
340✔
420
        };
340✔
421
    }
422

423
    /**
424
     * The factory method of the g(_, _[, _]) function with conditions.
425
     *
426
     * @param ConditionalRoleManager|null $crm
427
     *
428
     * @return Closure
429
     */
430
    public static function generateConditionalGFunction(?ConditionalRoleManager $crm = null): Closure
431
    {
432
        return function (...$args) use ($crm) {
40✔
433
            [$name1, $name2] = $args;
40✔
434

435
            if (is_null($crm)) {
40✔
436
                $v = $name1 == $name2;
×
437
            } elseif (2 == count($args)) {
40✔
438
                $v = $crm->hasLink($name1, $name2);
20✔
439
            } else {
440
                $domain = (string)$args[2];
30✔
441
                $v = $crm->hasLink($name1, $name2, $domain);
30✔
442
            }
443

444
            return $v;
40✔
445
        };
40✔
446
    }
447

448
    /**
449
     * The wrapper for timeMatch.
450
     *
451
     * @param mixed ...$args
452
     *
453
     * @return bool
454
     */
455
    public static function timeMatchFunc(...$args): bool
456
    {
457
        [$startTime, $endTime] = $args;
×
458

459
        return self::timeMatch($startTime, $endTime);
×
460
    }
461

462
    /**
463
     * Determines whether the current time is between startTime and endTime.
464
     * You can use "_" to indicate that the parameter is ignored.
465
     * 
466
     * @param string $startTime
467
     * @param string $endTime
468
     * 
469
     * @return bool
470
     */
471
    public static function timeMatch(string $startTime, string $endTime): bool
472
    {
473
        $now = new DateTime();
30✔
474
        if ($startTime !== '_') {
30✔
475
            if (false === strtotime($startTime)) {
20✔
476
                return false;
×
477
            }
478
            $start = new DateTime($startTime);
20✔
479
            if ($now < $start) {
20✔
480
                return false;
20✔
481
            }
482
        }
483
        if ($endTime !== '_') {
30✔
484
            if (false === strtotime($endTime)) {
30✔
485
                return false;
×
486
            }
487
            $end = new DateTime($endTime);
30✔
488
            if ($now > $end) {
30✔
489
                return false;
30✔
490
            }
491
        }
492

493
        return true;
30✔
494
    }
495
}
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