Completed
Pull Request — master (#1039)
by Matthew
01:29
created

PermissionRegistrar::getKey()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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