Completed
Push — master ( 9ae356...e44cb3 )
by Chris
01:55
created

PermissionRegistrar::getPermissionClass()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
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 Illuminate\Support\Facades\Cache;
7
use Spatie\Permission\Contracts\Role;
8
use Illuminate\Contracts\Auth\Access\Gate;
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
     */
44
    public function __construct(Gate $gate)
45
    {
46
        $this->gate = $gate;
47
        $this->permissionClass = config('permission.models.permission');
48
        $this->roleClass = config('permission.models.role');
49
50
        $this->initializeCache();
51
    }
52
53
    protected function initializeCache()
54
    {
55
        self::$cacheExpirationTime = config('permission.cache.expiration_time', config('permission.cache_expiration_time'));
56
        self::$cacheKey = config('permission.cache.key');
57
        self::$cacheModelKey = config('permission.cache.model_key');
58
59
        $cache = $this->getCacheStoreFromConfig();
60
61
        self::$cacheIsTaggable = ($cache->getStore() instanceof \Illuminate\Cache\TaggableStore);
62
63
        $this->cache = self::$cacheIsTaggable ? $cache->tags(self::$cacheKey) : $cache;
64
    }
65
66
    protected function getCacheStoreFromConfig(): \Illuminate\Contracts\Cache\Repository
67
    {
68
        // the 'default' fallback here is from the permission.php config file, where 'default' means to use config(cache.default)
69
        $cacheDriver = config('permission.cache.store', 'default');
70
71
        // when 'default' is specified, no action is required since we already have the default instance
72
        if ($cacheDriver === 'default') {
73
            return Cache::store();
74
        }
75
76
        // if an undefined cache store is specified, fallback to 'array' which is Laravel's closest equiv to 'none'
77
        if (! \array_key_exists($cacheDriver, config('cache.stores'))) {
78
            $cacheDriver = 'array';
79
        }
80
81
        return Cache::store($cacheDriver);
82
    }
83
84
    /**
85
     * Register the permission check method on the gate.
86
     *
87
     * @return bool
88
     */
89
    public function registerPermissions(): bool
90
    {
91
        $this->gate->before(function (Authorizable $user, string $ability) {
92
            try {
93
                if (method_exists($user, 'hasPermissionTo')) {
94
                    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...
95
                }
96
            } catch (PermissionDoesNotExist $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
97
            }
98
        });
99
100
        return true;
101
    }
102
103
    /**
104
     * Flush the cache.
105
     */
106
    public function forgetCachedPermissions()
107
    {
108
        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...
109
    }
110
111
    /**
112
     * Get the permissions based on the passed params.
113
     *
114
     * @param array $params
115
     *
116
     * @return \Illuminate\Support\Collection
117
     */
118
    public function getPermissions(array $params = []): Collection
119
    {
120
        $permissions = $this->cache->remember($this->getKey($params), self::$cacheExpirationTime,
121
            function () use ($params) {
122
                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...
123
                    ->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...
124
                        return $query->where($params);
125
                    })
126
                    ->with('roles')
127
                    ->get();
128
            });
129
130
        if (! self::$cacheIsTaggable) {
131
            foreach ($params as $attr => $value) {
132
                $permissions = $permissions->where($attr, $value);
133
            }
134
        }
135
136
        return $permissions;
137
    }
138
139
    /**
140
     * Get the key for caching.
141
     *
142
     * @param $params
143
     *
144
     * @return string
145
     */
146
    public function getKey(array $params): string
147
    {
148
        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...
149
            return self::$cacheKey.'.'.implode('.', array_values($params));
150
        }
151
152
        return self::$cacheKey;
153
    }
154
155
    /**
156
     * Get an instance of the permission class.
157
     *
158
     * @return \Spatie\Permission\Contracts\Permission
159
     */
160
    public function getPermissionClass(): Permission
161
    {
162
        return app($this->permissionClass);
163
    }
164
165
    /**
166
     * Get an instance of the role class.
167
     *
168
     * @return \Spatie\Permission\Contracts\Role
169
     */
170
    public function getRoleClass(): Role
171
    {
172
        return app($this->roleClass);
173
    }
174
175
    /**
176
     * Get the instance of the Cache Store.
177
     *
178
     * @return \Illuminate\Contracts\Cache\Store
179
     */
180
    public function getCacheStore(): \Illuminate\Contracts\Cache\Store
181
    {
182
        return $this->cache->getStore();
183
    }
184
}
185