Completed
Pull Request — master (#936)
by Chris
01:53
created

PermissionRegistrar::forgetCachedPermissions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace Spatie\Permission;
4
5
use Illuminate\Support\Collection;
6
use Spatie\Permission\Contracts\Role;
7
use Illuminate\Contracts\Auth\Access\Gate;
8
use Illuminate\Contracts\Cache\Repository;
9
use Spatie\Permission\Contracts\Permission;
10
use Illuminate\Contracts\Auth\Access\Authorizable;
11
use Spatie\Permission\Exceptions\PermissionDoesNotExist;
12
13
class PermissionRegistrar
14
{
15
    /** @var \Illuminate\Contracts\Auth\Access\Gate */
16
    protected $gate;
17
18
    /** @var \Illuminate\Contracts\Cache\Repository */
19
    protected $cache;
20
21
    /** @var string */
22
    protected $permissionClass;
23
24
    /** @var string */
25
    protected $roleClass;
26
27
    /** @var int */
28
    public static $cacheExpirationTime;
29
30
    /** @var string */
31
    public static $cacheKey;
32
33
    /** @var string */
34
    public static $cacheModelKey;
35
36
    /** @var bool */
37
    public static $cacheIsTaggable = false;
38
39
    /**
40
     * PermissionRegistrar constructor.
41
     *
42
     * @param \Illuminate\Contracts\Auth\Access\Gate $gate
43
     * @param \Illuminate\Contracts\Cache\Repository $cache
44
     */
45
    public function __construct(Gate $gate, Repository $cache)
46
    {
47
        $this->gate = $gate;
48
        $this->permissionClass = config('permission.models.permission');
49
        $this->roleClass = config('permission.models.role');
50
51
        self::$cacheExpirationTime = config('permission.cache.expiration_time', config('permission.cache_expiration_time'));
52
        self::$cacheKey = config('permission.cache.key');
53
        self::$cacheModelKey = config('permission.cache.model_key');
54
        self::$cacheIsTaggable = ($cache->getStore() instanceof \Illuminate\Cache\TaggableStore);
55
56
        $this->cache = self::$cacheIsTaggable ? $cache->tags(self::$cacheKey) : $cache;
57
    }
58
59
    /**
60
     * Register the permission check method on the gate.
61
     *
62
     * @return bool
63
     */
64
    public function registerPermissions(): bool
65
    {
66
        $this->gate->before(function (Authorizable $user, string $ability) {
67
            try {
68
                if (method_exists($user, 'hasPermissionTo')) {
69
                    return $user->hasPermissionTo($ability) ?: null;
0 ignored issues
show
Bug introduced by
The method hasPermissionTo() does not seem to exist on object<Illuminate\Contra...th\Access\Authorizable>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
70
                }
71
            } catch (PermissionDoesNotExist $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
72
            }
73
        });
74
75
        return true;
76
    }
77
78
    /**
79
     * Flush the cache.
80
     */
81
    public function forgetCachedPermissions()
82
    {
83
        self::$cacheIsTaggable ? $this->cache->flush() : $this->cache->forget(self::$cacheKey);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Cache\Repository as the method flush() does only exist in the following implementations of said interface: Illuminate\Cache\RedisTaggedCache, Illuminate\Cache\TaggedCache.

Let’s take a look at an example:

interface User
{
    /** @return string */
    public function getPassword();
}

class MyUser implements User
{
    public function getPassword()
    {
        // return something
    }

    public function getDisplayName()
    {
        // return some name.
    }
}

class AuthSystem
{
    public function authenticate(User $user)
    {
        $this->logger->info(sprintf('Authenticating %s.', $user->getDisplayName()));
        // do something.
    }
}

In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.

Available Fixes

  1. Change the type-hint for the parameter:

    class AuthSystem
    {
        public function authenticate(MyUser $user) { /* ... */ }
    }
    
  2. Add an additional type-check:

    class AuthSystem
    {
        public function authenticate(User $user)
        {
            if ($user instanceof MyUser) {
                $this->logger->info(/** ... */);
            }
    
            // or alternatively
            if ( ! $user instanceof MyUser) {
                throw new \LogicException(
                    '$user must be an instance of MyUser, '
                   .'other instances are not supported.'
                );
            }
    
        }
    }
    
Note: PHP Analyzer uses reverse abstract interpretation to narrow down the types inside the if block in such a case.
  1. Add the method to the interface:

    interface User
    {
        /** @return string */
        public function getPassword();
    
        /** @return string */
        public function getDisplayName();
    }
    
Loading history...
84
    }
85
86
    /**
87
     * Get the permissions based on the passed params.
88
     *
89
     * @param array $params
90
     *
91
     * @return \Illuminate\Support\Collection
92
     */
93
    public function getPermissions(array $params = []): Collection
94
    {
95
        $permissions = $this->cache->remember($this->getKey($params), self::$cacheExpirationTime,
96
            function () use ($params) {
97
                return $this->getPermissionClass()
0 ignored issues
show
Bug introduced by
The method when() does not seem to exist on object<Spatie\Permission\Contracts\Permission>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
98
                    ->when(($params && self::$cacheIsTaggable), function ($query) use ($params) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
99
                        return $query->where($params);
100
                    })
101
                    ->with('roles')
102
                    ->get();
103
            });
104
105
        if (! self::$cacheIsTaggable) {
106
            foreach ($params as $attr => $value) {
107
                $permissions = $permissions->where($attr, $value);
108
            }
109
        }
110
111
        return $permissions;
112
    }
113
114
    /**
115
     * Get the key for caching.
116
     *
117
     * @param $params
118
     *
119
     * @return string
120
     */
121
    public function getKey(array $params): string
122
    {
123
        if ($params && self::$cacheIsTaggable) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $params of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
124
            return self::$cacheKey.'.'.implode('.', array_values($params));
125
        }
126
127
        return self::$cacheKey;
128
    }
129
130
    /**
131
     * Get an instance of the permission class.
132
     *
133
     * @return \Spatie\Permission\Contracts\Permission
134
     */
135
    public function getPermissionClass(): Permission
136
    {
137
        return app($this->permissionClass);
138
    }
139
140
    /**
141
     * Get an instance of the role class.
142
     *
143
     * @return \Spatie\Permission\Contracts\Role
144
     */
145
    public function getRoleClass(): Role
146
    {
147
        return app($this->roleClass);
148
    }
149
150
    /**
151
     * Get the instance of the Cache Store
152
     *
153
     * @return \Illuminate\Contracts\Cache\Store
154
     */
155
    public function getCacheStore(): \Illuminate\Contracts\Cache\Store
156
    {
157
        return $this->cache->getStore();
158
    }
159
}
160