Completed
Push — master ( 74eea4...7541a9 )
by Chris
14s
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\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',
52
            config('permission.cache_expiration_time'));
53
        self::$cacheKey = config('permission.cache.key');
54
        self::$cacheModelKey = config('permission.cache.model_key');
55
        self::$cacheIsTaggable = ($cache->getStore() instanceof \Illuminate\Cache\TaggableStore);
56
57
        $this->cache = self::$cacheIsTaggable ? $cache->tags(self::$cacheKey) : $cache;
58
    }
59
60
    /**
61
     * Register the permission check method on the gate.
62
     *
63
     * @return bool
64
     */
65
    public function registerPermissions(): bool
66
    {
67
        $this->gate->before(function (Authorizable $user, string $ability) {
68
            try {
69
                if (method_exists($user, 'hasPermissionTo')) {
70
                    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...
71
                }
72
            } catch (PermissionDoesNotExist $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
73
            }
74
        });
75
76
        return true;
77
    }
78
79
    /**
80
     * Flush the cache.
81
     */
82
    public function forgetCachedPermissions()
83
    {
84
        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...
85
    }
86
87
    /**
88
     * Get the permissions based on the passed params.
89
     *
90
     * @param array $params
91
     *
92
     * @return \Illuminate\Support\Collection
93
     */
94
    public function getPermissions(array $params = []): Collection
95
    {
96
        $permissions = $this->cache->remember($this->getKey($params), self::$cacheExpirationTime,
97
            function () use ($params) {
98
                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...
99
                    ->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...
100
                        return $query->where($params);
101
                    })
102
                    ->with('roles')
103
                    ->get();
104
            });
105
106
        if (! self::$cacheIsTaggable) {
107
            foreach ($params as $attr => $value) {
108
                $permissions = $permissions->where($attr, $value);
109
            }
110
        }
111
112
        return $permissions;
113
    }
114
115
    /**
116
     * Get the key for caching.
117
     *
118
     * @param $params
119
     *
120
     * @return string
121
     */
122
    public function getKey(array $params): string
123
    {
124
        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...
125
            return self::$cacheKey.'.'.implode('.', array_values($params));
126
        }
127
128
        return self::$cacheKey;
129
    }
130
131
    /**
132
     * Get an instance of the permission class.
133
     *
134
     * @return \Spatie\Permission\Contracts\Permission
135
     */
136
    public function getPermissionClass(): Permission
137
    {
138
        return app($this->permissionClass);
139
    }
140
141
    /**
142
     * Get an instance of the role class.
143
     *
144
     * @return \Spatie\Permission\Contracts\Role
145
     */
146
    public function getRoleClass(): Role
147
    {
148
        return app($this->roleClass);
149
    }
150
}
151