-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathGenericPermissionsCollection.php
371 lines (306 loc) · 13.9 KB
/
GenericPermissionsCollection.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
<?php
declare(strict_types=1);
namespace VersatileAcl;
use VersatileAcl\Interfaces\PermissionInterface;
use VersatileAcl\Interfaces\PermissionsCollectionInterface;
use function array_key_exists;
use function uasort;
/**
* @property PermissionInterface[] $storage
*/
class GenericPermissionsCollection extends GenericBaseCollection implements PermissionsCollectionInterface {
/**
* Constructor.
*
* @param PermissionInterface ...$permissions zero or more instances of PermissionInterface to be added to this collection
*
*/
public function __construct(PermissionInterface ...$permissions) {
$this->storage = $permissions;
}
/**
* Checks whether or not a permission exists in the current instance.
*
* `$perm` is present in the current instance if there is another permission `$x`
* in the current instance where $x->isEqualTo($perm) === true.
*
*
* @return bool true if there is another permission `$x` in the current instance where $x->isEqualTo($perm) === true, otherwise return false
*/
public function hasPermission(PermissionInterface $permission): bool {
/** @var PermissionInterface $other_permission **/
foreach ($this->storage as $other_permission) {
if( $permission->isEqualTo($other_permission) ) {
return true;
}
}
return false;
}
/**
* Calculates and returns a boolean value indicating whether or not one or more items in an instance of this class
* signifies that a specified action can be performed on a specified resource.
*
* This method should return true (signifying that the specified action is regarded as performable on the specified resource) only if:
* - an item `$x` exists in an instance of this class
* - and $x->getAction() === $action
* - and $x->getResource() === $resource
* - and $x->getAllowActionOnResource() === true
* - and if supplied, executing $additionalAssertions with $argsForCallback as arguments must also return true
*
* @param string $action an action to check whether or not it can be performed on the specified resource
* @param string $resource a resource we are testing whether an action can be performed on
* @param callable|null $additionalAssertions an optional callback function with additional tests to check whether the specified action can be performed on the specified resource.
* The callback must return true if the specified action can be performed on the specified resource.
* @param mixed ...$argsForCallback optional arguments that may be required by the $additionalAssertions callback
*
* @return bool return true if one or more items in an instance of this class signifies that a specified action can be performed on a specified resource, or false otherwise
*/
public function isAllowed(string $action, string $resource, callable $additionalAssertions = null, mixed ...$argsForCallback): bool {
/** @var PermissionInterface $permission */
foreach ($this->storage as $permission) {
/** @var PermissionInterface $permissionClass */
$permissionClass = $permission::class; // for late static binding
if(
(
Utils::strSameIgnoreCase($permission->getAction(), $action)
||
Utils::strSameIgnoreCase($permission->getAction(), $permissionClass::getAllActionsIdentifier())
)
&&
(
Utils::strSameIgnoreCase($permission->getResource(), $resource)
||
Utils::strSameIgnoreCase($permission->getResource(), $permissionClass::getAllResourcesIdentifier())
)
) {
return $permission->isAllowed($action, $resource, $additionalAssertions, ...$argsForCallback);
}
}
return false;
}
/**
* Adds an instance of PermissionInterface to an instance of this class.
*
*
*
* @return $this
*/
public function add(PermissionInterface $permission): PermissionsCollectionInterface {
if( !$this->hasPermission($permission) ) {
$this->storage[] = $permission;
} else {
// update the existing permission
$key = $this->getKey($permission);
$key !== null && $this->put($permission, ''.$key);
}
return $this;
}
/**
* Retrieves the key in the collection associated with the specified permission object.
* If the object is not present in the collection, NULL should be returned
*
*
* @return string|int|null
*/
public function getKey(PermissionInterface $permission) {
/** @var PermissionInterface $other_permission */
foreach ($this->storage as $key => $other_permission) {
if( $permission->isEqualTo($other_permission) ) {
return $key;
}
}
return null;
}
/**
* Removes an instance of PermissionInterface from an instance of this class.
*
*
*
* @return $this
*/
public function remove(PermissionInterface $permission): PermissionsCollectionInterface {
$key = $this->getKey($permission);
if($key !== null) {
$this->removeByKey($key);
}
return $this;
}
/**
* Remove all items in the collection and return $this
*
*
* @return $this
*/
public function removeAll(): PermissionsCollectionInterface {
$this->storage = [];
return $this;
}
/**
* Adds an instance of PermissionInterface to an instance of this class with the specified key.
*
* @param string $key specified key for $permission in the collection
*
*
* @return $this
*/
public function put(PermissionInterface $permission, string $key): PermissionsCollectionInterface {
$this->storage[$key] = $permission;
return $this;
}
/**
* Retrieves the permission in the collection associated with the specified key.
* If the key is not present in the collection, NULL should be returned
*
*
*/
public function get(string $key): ?PermissionInterface {
/** @var PermissionInterface $this->storage[$key] **/
return array_key_exists($key, $this->storage) ? $this->storage[$key] : null;
}
/**
* Sort the collection.
* If specified, use the callback to compare items in the collection when sorting or
* sort according to some default criteria (up to the implementer of this method to
* specify what that criteria is).
*
* If $comparator is null, this implementation would sort based on ascending order
* of PermissionInterface::getResource() followed by
* PermissionInterface::getAction() and followed by
* PermissionInterface::getAllowActionOnResource()
* of each permission in the collection.
*
* @param callable|null $comparator has the following signature:
* function( PermissionInterface $a, PermissionInterface $b ) : int
* The comparison function must return an integer less than,
* equal to, or greater than zero if the first argument is
* considered to be respectively less than, equal to,
* or greater than the second.
*
*
* @return $this
* @noinspection PhpDocSignatureInspection
*/
public function sort(callable $comparator = null): PermissionsCollectionInterface {
if( $comparator === null ) {
$comparator = function(PermissionInterface $a, PermissionInterface $b ) : int {
if( $a->getResource() < $b->getResource() ) {
return -1;
} else if( $a->getResource() === $b->getResource() ) {
if( $a->getAction() < $b->getAction() ) {
return -1;
} else if ( $a->getAction() === $b->getAction() ) {
if( $a->getAllowActionOnResource() < $b->getAllowActionOnResource() ) {
return -1;
} elseif( $a->getAllowActionOnResource() === $b->getAllowActionOnResource() ) {
return 0;
} // if( $a->getAllowActionOnResource() < $b->getAllowActionOnResource() ) ... elseif( $a->getAllowActionOnResource() === $b->getAllowActionOnResource() )
} // if( $a->getAction() < $b->getAction() ) ... else if ( $a->getAction() === $b->getAction() )
} // if( $a->getResource() < $b->getResource() ) ... else if( $a->getResource() === $b->getResource() )
return 1;
};
}
/** @var array<string, PermissionInterface> $this->storage **/
/** @var callable(mixed, mixed):int $comparator **/
uasort($this->storage, $comparator);
return $this;
}
/**
* Find and return the first permission in the collection that matches the specified $action and / or $resource.
* NULL should be returned if there was no match.
* The match should be case-insensitive
* If $action has an empty string value, do not use it for the match
* If $resource has an empty string value, do not use it for the match
* If both $action & $resource have empty string values, return NULL
*
*
* @noinspection PhpFullyQualifiedNameUsageInspection
* @noinspection PhpRedundantOptionalArgumentInspection
* @throws \Exception
*/
public function findOne(string $action='', string $resource=''): ?PermissionInterface {
/** @var array<int|string, PermissionInterface> $firstMatch **/
$firstMatch =
\iterator_to_array(
$this->findFirstN($action, $resource, 1)
->getIterator()
);
/** @noinspection PhpFullyQualifiedNameUsageInspection */
return (\count($firstMatch) > 0) ? \array_shift($firstMatch) : null;
}
/**
* Find and return the all permissions in the collection that match the specified $action and / or $resource.
* An empty collection should be returned if there was no match.
* The match should be case-insensitive
* If $action has an empty string value, do not use it for the match
* If $resource has an empty string value, do not use it for the match
* If both $action & $resource have empty string values, return an empty collection
*
*
* @noinspection DuplicatedCode
*/
public function findAll(string $action='', string $resource=''): PermissionsCollectionInterface {
// can find at most $this->count() permissions matching the parameters
return $this->findFirstN($action, $resource, $this->count());
}
/**
* Find and return the all permissions in the collection that match the specified $action and / or $resource.
* An empty collection should be returned if there was no match. The match is always case-insensitive
*
* If $action has an empty string value, do not use it for the match
*
* If $resource has an empty string value, do not use it for the match
*
* If both $action & $resource have empty string values, return an empty collection
*
* If $n < 1, this method internally bumps it up to 1 and returns either
* a collection contain only 1 matching permission object or an empty collection
*
* @param int $n number of matching permission objects to be returned
*
*
* @noinspection DuplicatedCode
* @psalm-suppress RedundantCondition
* @psalm-suppress UnsafeInstantiation
*
*/
protected function findFirstN(string $action='', string $resource='', int $n=1): PermissionsCollectionInterface {
$permissionsCollection = new static();
if($n < 1) { // we must try to find at least 1 permission
$n = 1;
}
if(($action !== '' || $resource !== '') && $this->count() > 0 ) {
$counter = 1;
/** @var PermissionInterface $permission */
foreach ($this->storage as $permission) {
if(
(
// match by $action and $resource
$action !== ''
&& $resource !== ''
&& Utils::strSameIgnoreCase($permission->getAction(), $action)
&& Utils::strSameIgnoreCase($permission->getResource(), $resource)
)
||
(
// only match by $resource
$action === ''
&& $resource !== ''
&& Utils::strSameIgnoreCase($permission->getResource(), $resource)
)
||
(
// only match by $action
$action !== ''
&& $resource === ''
&& Utils::strSameIgnoreCase($permission->getAction(), $action)
)
) {
$permissionsCollection->add($permission);
$counter++;
}
if($counter > $n) { break; } // found first N permissions
} // foreach ($this->storage as $permission)
} // if( !($action === '' && $resource === '') )
return $permissionsCollection;
}
}