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

php-casbin / database-adapter / 11569185946

29 Oct 2024 07:28AM UTC coverage: 92.466% (-0.3%) from 92.806%
11569185946

Pull #25

github

web-flow
Merge d2f0e2bd3 into 05a3ce7e6
Pull Request #25: BREAKING CHANGE: upgrade to PHP 8.0 and PHP-Casbin 4.0

135 of 146 relevant lines covered (92.47%)

64.6 hits per line

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

92.47
/src/Adapter.php
1
<?php
2

3
namespace CasbinAdapter\Database;
4

5
use Casbin\Model\Model;
6
use Casbin\Persist\Adapter as AdapterContract;
7
use Leeqvip\Database\Manager;
8
use Casbin\Persist\AdapterHelper;
9
use Casbin\Persist\FilteredAdapter as FilteredAdapterContract;
10
use Casbin\Persist\Adapters\Filter;
11
use Casbin\Exceptions\InvalidFilterTypeException;
12
use Casbin\Persist\BatchAdapter as BatchAdapterContract;
13
use Casbin\Persist\UpdatableAdapter as UpdatableAdapterContract;
14
use Closure;
15
use Throwable;
16

17
/**
18
 * DatabaseAdapter.
19
 *
20
 * @author techlee@qq.com
21
 */
22
class Adapter implements AdapterContract, FilteredAdapterContract, BatchAdapterContract, UpdatableAdapterContract
23
{
24
    use AdapterHelper;
25

26
    protected $config;
27

28
    protected $filtered;
29

30
    protected $connection;
31

32
    public $policyTableName = 'casbin_rule';
33

34
    public $rows = [];
35

36
    public function __construct(array $config)
37
    {
38
        $this->config = $config;
264✔
39
        $this->filtered = false;
264✔
40
        $this->connection = (new Manager($config))->getConnection();
264✔
41

42
        if (isset($config['policy_table_name']) && !is_null($config['policy_table_name'])) {
264✔
43
            $this->policyTableName = $config['policy_table_name'];
×
44
        }
45

46
        $this->initTable();
264✔
47
    }
48

49
    /**
50
     * Returns true if the loaded policy has been filtered.
51
     *
52
     * @return bool
53
     */
54
    public function isFiltered(): bool
55
    {
56
        return $this->filtered;
264✔
57
    }
58

59
    /**
60
     * Sets filtered parameter.
61
     *
62
     * @param bool $filtered
63
     */
64
    public function setFiltered(bool $filtered): void
65
    {
66
        $this->filtered = $filtered;
24✔
67
    }
68

69
    /**
70
     * Filter the rule.
71
     *
72
     * @param array $rule
73
     * @return array
74
     */
75
    public function filterRule(array $rule): array
76
    {
77
        $rule = array_values($rule);
264✔
78

79
        $i = count($rule) - 1;
264✔
80
        for (; $i >= 0; $i--) {
264✔
81
            if ($rule[$i] != '' && !is_null($rule[$i])) {
264✔
82
                break;
264✔
83
            }
84
        }
85

86
        return array_slice($rule, 0, $i + 1);
264✔
87
    }
88

89
    public static function newAdapter(array $config)
90
    {
91
        return new static($config);
264✔
92
    }
93

94
    public function initTable()
95
    {
96
        $sql = file_get_contents(__DIR__.'/../migrations/'.$this->config['type'].'.sql');
264✔
97
        $sql = str_replace('%table_name%', $this->policyTableName, $sql);
264✔
98
        $this->connection->execute($sql, []);
264✔
99
    }
100

101
    public function savePolicyLine($ptype, array $rule)
102
    {
103
        $col['ptype'] = $ptype;
72✔
104
        foreach ($rule as $key => $value) {
72✔
105
            $col['v'.strval($key).''] = $value;
72✔
106
        }
107

108
        $colStr = implode(', ', array_keys($col));
72✔
109

110
        $name = rtrim(str_repeat('?, ', count($col)), ', ');
72✔
111

112
        $sql = 'INSERT INTO '.$this->policyTableName.'('.$colStr.') VALUES ('.$name.') ';
72✔
113

114
        $this->connection->execute($sql, array_values($col));
72✔
115
    }
116

117
    /**
118
     * loads all policy rules from the storage.
119
     *
120
     * @param Model $model
121
     */
122
    public function loadPolicy(Model $model): void
123
    {
124
        $rows = $this->connection->query('SELECT ptype, v0, v1, v2, v3, v4, v5 FROM '.$this->policyTableName.'');
264✔
125

126
        foreach ($rows as $row) {
264✔
127
            $this->loadPolicyArray($this->filterRule($row), $model);
264✔
128
        }
129
    }
130

131
    /**
132
     * saves all policy rules to the storage.
133
     *
134
     * @param Model $model
135
     */
136
    public function savePolicy(Model $model): void
137
    {
138
        foreach ($model['p'] as $ptype => $ast) {
24✔
139
            foreach ($ast->policy as $rule) {
24✔
140
                $this->savePolicyLine($ptype, $rule);
24✔
141
            }
142
        }
143

144
        foreach ($model['g'] as $ptype => $ast) {
24✔
145
            foreach ($ast->policy as $rule) {
24✔
146
                $this->savePolicyLine($ptype, $rule);
×
147
            }
148
        }
149
    }
150

151
    /**
152
     * adds a policy rule to the storage.
153
     * This is part of the Auto-Save feature.
154
     *
155
     * @param string $sec
156
     * @param string $ptype
157
     * @param array  $rule
158
     */
159
    public function addPolicy(string $sec, string $ptype, array $rule): void
160
    {
161
        $this->savePolicyLine($ptype, $rule);
48✔
162
    }
163

164
    public function addPolicies(string $sec, string $ptype, array $rules): void
165
    {
166
        $table = $this->policyTableName;
48✔
167
        $columns = ['ptype', 'v0', 'v1', 'v2', 'v3', 'v4', 'v5'];
48✔
168
        $values = [];
48✔
169
        $sets = [];
48✔
170
        $columnsCount = count($columns);
48✔
171
        foreach ($rules as $rule) {
48✔
172
            array_unshift($rule, $ptype);
48✔
173
            $values = array_merge($values, array_pad($rule, $columnsCount, null));
48✔
174
            $sets[] = array_pad([], $columnsCount, '?');
48✔
175
        }
176
        $valuesStr = implode(', ', array_map(function ($set) {
48✔
177
            return '(' . implode(', ', $set) . ')';
48✔
178
        }, $sets));
48✔
179
        $sql = 'INSERT INTO ' . $table . ' (' . implode(', ', $columns) . ')' .
48✔
180
            ' VALUES' . $valuesStr;
48✔
181
        $this->connection->execute($sql, $values);
48✔
182
    }
183

184
    public function removePolicies(string $sec, string $ptype, array $rules): void
185
    {
186
        $this->connection->getPdo()->beginTransaction();
24✔
187
        try {
188
            foreach ($rules as $rule) {
24✔
189
                $this->removePolicy($sec, $ptype, $rule);
24✔
190
            }
191
            $this->connection->getPdo()->commit();
24✔
192
        } catch (Throwable $e) {
×
193
            $this->connection->getPdo()->rollback();
×
194
            throw $e;
×
195
        }
196
    }
197

198
    /**
199
     * This is part of the Auto-Save feature.
200
     *
201
     * @param string $sec
202
     * @param string $ptype
203
     * @param array  $rule
204
     */
205
    public function removePolicy(string $sec, string $ptype, array $rule): void
206
    {
207
        $where['ptype'] = $ptype;
48✔
208
        $condition[] = 'ptype = :ptype';
48✔
209
        foreach ($rule as $key => $value) {
48✔
210
            $where['v'.strval($key)] = $value;
48✔
211
            $condition[] = 'v'.strval($key).' = :'.'v'.strval($key);
48✔
212
        }
213

214
        $sql = 'DELETE FROM '.$this->policyTableName.' WHERE '.implode(' AND ', $condition);
48✔
215

216
        $this->connection->execute($sql, $where);
48✔
217
    }
218

219
    /**
220
     * @param string $sec
221
     * @param string $ptype
222
     * @param int $fieldIndex
223
     * @param string|null ...$fieldValues
224
     * @return array
225
     * @throws Throwable
226
     */
227
    public function _removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, ?string ...$fieldValues): array
228
    {
229
        $removedRules = [];
48✔
230
        $where['ptype'] = $ptype;
48✔
231
        $condition[] = 'ptype = :ptype';
48✔
232
        foreach (range(0, 5) as $value) {
48✔
233
            if ($fieldIndex <= $value && $value < $fieldIndex + count($fieldValues)) {
48✔
234
                if ('' != $fieldValues[$value - $fieldIndex]) {
48✔
235
                    $where['v'.strval($value)] = $fieldValues[$value - $fieldIndex];
48✔
236
                    $condition[] = 'v'.strval($value).' = :'.'v'.strval($value);
48✔
237
                }
238
            }
239
        }
240

241
        $deleteSql = "DELETE FROM {$this->policyTableName} WHERE " . implode(' AND ', $condition);
48✔
242

243
        $selectSql = "SELECT * FROM {$this->policyTableName} WHERE " . implode(' AND ', $condition);
48✔
244

245
        $oldP = $this->connection->query($selectSql, $where);
48✔
246
        foreach ($oldP as &$item) {
48✔
247
            unset($item['ptype']);
48✔
248
            unset($item['id']);
48✔
249
            $item = $this->filterRule($item);
48✔
250
            $removedRules[] = $item;
48✔
251
        }
252

253
        $this->connection->execute($deleteSql, $where);
48✔
254
        return $removedRules;
48✔
255
    }
256

257
    /**
258
      * RemoveFilteredPolicy removes policy rules that match the filter from the storage.
259
      * This is part of the Auto-Save feature.
260
      *
261
      * @param string $sec
262
      * @param string $ptype
263
      * @param int $fieldIndex
264
      * @param string ...$fieldValues
265
      * @throws Exception|Throwable
266
      */
267
    public function removeFilteredPolicy(string $sec, string $ptype, int $fieldIndex, string ...$fieldValues): void
268
    {
269
        $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
24✔
270
    }
271

272
    /**
273
     * Loads only policy rules that match the filter from storage.
274
     *
275
     * @param Model $model
276
     * @param mixed $filter
277
     *
278
     * @throws CasbinException
279
     */
280
    public function loadFilteredPolicy(Model $model, $filter): void
281
    {
282
        // the basic sql
283
        $sql = 'SELECT ptype, v0, v1, v2, v3, v4, v5 FROM '.$this->policyTableName . ' WHERE ';
24✔
284

285
        $bind = [];
24✔
286

287
        if (is_string($filter)) {
24✔
288
            $filter = str_replace(' ', '', $filter);
24✔
289
            $filter = str_replace('\'', '', $filter);
24✔
290
            $filter = explode('=', $filter);
24✔
291
            $sql .= "$filter[0] = :{$filter[0]}";
24✔
292
            $bind[$filter[0]] = $filter[1];
24✔
293
        } elseif ($filter instanceof Filter) {
24✔
294
            foreach ($filter->p as $k => $v) {
24✔
295
                $where[] = $v . ' = :' . $v;
24✔
296
                $bind[$v] = $filter->g[$k];
24✔
297
            }
298
            $where = implode(' AND ', $where);
24✔
299
            $sql .= $where;
24✔
300
        } elseif ($filter instanceof Closure) {
24✔
301
            $where = '';
24✔
302
            $filter($where);
24✔
303
            $where = str_replace(' ', '', $where);
24✔
304
            $where = str_replace('\'', '', $where);
24✔
305
            $where = explode('=', $where);
24✔
306
            $sql .= "$where[0] = :{$where[0]}";
24✔
307
            $bind[$where[0]] = $where[1];
24✔
308
        } else {
309
            throw new InvalidFilterTypeException('invalid filter type');
24✔
310
        }
311

312
        $rows = $this->connection->query($sql, $bind);
24✔
313
        foreach ($rows as $row) {
24✔
314
            $row = array_filter($row, function ($value) {
24✔
315
                return !is_null($value) && $value !== '';
24✔
316
            });
24✔
317
            unset($row['id']);
24✔
318
            $line = implode(', ', array_filter($row, function ($val) {
24✔
319
                return '' != $val && !is_null($val);
24✔
320
            }));
24✔
321
            $this->loadPolicyLine(trim($line), $model);
24✔
322
        }
323

324
        $this->setFiltered(true);
24✔
325
    }
326

327
    /**
328
     * Updates a policy rule from storage.
329
     * This is part of the Auto-Save feature.
330
     *
331
     * @param string $sec
332
     * @param string $ptype
333
     * @param string[] $oldRule
334
     * @param string[] $newPolicy
335
     */
336
    public function updatePolicy(string $sec, string $ptype, array $oldRule, array $newPolicy): void
337
    {
338
        $where['ptype'] = $ptype;
48✔
339
        $condition[] = 'ptype = :ptype';
48✔
340

341
        foreach ($oldRule as $key => $value) {
48✔
342
            $placeholder = "w" . strval($key);
48✔
343
            $where['w' . strval($key)] = $value;
48✔
344
            $condition[] = 'v' . strval($key) . ' = :' . $placeholder;
48✔
345
        }
346

347
        $update = [];
48✔
348
        foreach ($newPolicy as $key => $value) {
48✔
349
            $placeholder = "s" . strval($key);
48✔
350
            $updateValue["$placeholder"] = $value;
48✔
351
            $update[] = 'v' . strval($key) . ' = :' . $placeholder;
48✔
352
        }
353

354
        $sql = "UPDATE {$this->policyTableName} SET " . implode(', ', $update) . " WHERE " . implode(' AND ', $condition);
48✔
355

356
        $this->connection->execute($sql, array_merge($updateValue, $where));
48✔
357
    }
358

359
    /**
360
     * UpdatePolicies updates some policy rules to storage, like db, redis.
361
     *
362
     * @param string $sec
363
     * @param string $ptype
364
     * @param string[][] $oldRules
365
     * @param string[][] $newRules
366
     * @return void
367
     */
368
    public function updatePolicies(string $sec, string $ptype, array $oldRules, array $newRules): void
369
    {
370
        $this->connection->getPdo()->beginTransaction();
24✔
371
        try {
372
            foreach ($oldRules as $i => $oldRule) {
24✔
373
                $this->updatePolicy($sec, $ptype, $oldRule, $newRules[$i]);
24✔
374
            }
375
            $this->connection->getPdo()->commit();
24✔
376
        } catch (Throwable $e) {
×
377
            $this->connection->getPdo()->rollback();
×
378
            throw $e;
×
379
        }
380
    }
381

382
    /**
383
     * @param string $sec
384
     * @param string $ptype
385
     * @param array $newRules
386
     * @param int $fieldIndex
387
     * @param string ...$fieldValues
388
     * @return array
389
     * @throws Throwable
390
     */
391
    public function updateFilteredPolicies(string $sec, string $ptype, array $newRules, int $fieldIndex, ?string ...$fieldValues): array
392
    {
393
        $oldRules = [];
24✔
394
        $this->connection->getPdo()->beginTransaction();
24✔
395
        try {
396
            $oldRules = $this->_removeFilteredPolicy($sec, $ptype, $fieldIndex, ...$fieldValues);
24✔
397
            $this->addPolicies($sec, $ptype, $newRules);
24✔
398
            $this->connection->getPdo()->commit();
24✔
399
        } catch (Throwable $e) {
×
400
            $this->connection->getPdo()->rollback();
×
401
            throw $e;
×
402
        }
403

404
        return $oldRules;
24✔
405
    }
406
}
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