Completed
Push — master ( 2da229...6c27e6 )
by Hamidreza
01:12
created

File::scopeWhereOwnerIdIs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
1
<?php
2
3
namespace Hamidrezaniazi\Upolo\Models;
4
5
use Hamidrezaniazi\Upolo\Contracts\HasFileInterface;
6
use Hamidrezaniazi\Upolo\Filters\FileFilters;
7
use Hamidrezaniazi\Upolo\Guard;
8
use Illuminate\Contracts\Auth\Authenticatable as User;
9
use Illuminate\Database\Eloquent\Builder;
10
use Illuminate\Database\Eloquent\Model;
11
use Illuminate\Database\Eloquent\Relations\BelongsTo;
12
use Illuminate\Database\Eloquent\Relations\MorphTo;
13
use Illuminate\Http\UploadedFile;
14
use Illuminate\Support\Facades\Storage;
15
use Illuminate\Support\Str;
16
17
/**
18
 * Class File.
19
 *
20
 * @property int id
21
 * @property string uuid
22
 * @property string path
23
 * @property string disk
24
 * @property string filename
25
 * @property string type
26
 * @property string flag
27
 * @property string mime
28
 * @property string url
29
 * @property HasFileInterface owner
30
 * @property User creator
31
 */
32
class File extends Model
33
{
34
    protected $appends = ['url'];
35
36
    /**
37
     * @return BelongsTo
38
     */
39
    public function creator(): BelongsTo
40
    {
41
        return $this->belongsTo(Guard::getGuardClassName());
42
    }
43
44
    /**
45
     * @return MorphTo
46
     */
47
    public function owner(): MorphTo
48
    {
49
        return $this->morphTo();
50
    }
51
52
    /**
53
     * Apply all relevant thread filters.
54
     *
55
     * @param Builder $query
56
     * @param FileFilters $filters
57
     * @return Builder
58
     */
59
    public function scopeFilter(Builder $query, FileFilters $filters): Builder
60
    {
61
        return $filters->apply($query);
62
    }
63
64
    /**
65
     * @param Builder $query
66
     * @param HasFileInterface $owner
67
     * @return Builder
68
     */
69
    public function scopeWhereOwnerIs(Builder $query, HasFileInterface $owner): Builder
70
    {
71
        return $query->where('owner_type', $owner->getMorphClass())->where('owner_id', $owner->getKey());
0 ignored issues
show
Bug introduced by
The method getMorphClass() does not seem to exist on object<Hamidrezaniazi\Up...racts\HasFileInterface>.

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...
Bug introduced by
The method getKey() does not seem to exist on object<Hamidrezaniazi\Up...racts\HasFileInterface>.

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...
72
    }
73
74
    /**
75
     * @param Builder $query
76
     * @param int $ownerId
77
     * @return Builder
78
     */
79
    public function scopeWhereOwnerIdIs(Builder $query, int $ownerId): Builder
80
    {
81
        return $query->where('owner_id', $ownerId);
82
    }
83
84
    /**
85
     * @param Builder $query
86
     * @param string $ownerType
87
     * @return Builder
88
     */
89
    public function scopeWhereOwnerTypeIs(Builder $query, string $ownerType): Builder
90
    {
91
        return $query->where('owner_type', $ownerType);
92
    }
93
94
    /**
95
     * @param Builder $query
96
     * @param User $creator
97
     * @return Builder
98
     */
99
    public function scopeWhereCreatorIs(Builder $query, User $creator): Builder
100
    {
101
        return $query->where('creator_id', $creator->getKey());
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Auth\Authenticatable as the method getKey() does only exist in the following implementations of said interface: Illuminate\Foundation\Auth\User.

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...
102
    }
103
104
    /**
105
     * @param Builder $query
106
     * @param int $creatorId
107
     * @return Builder
108
     */
109
    public function scopeWhereCreatorIdIs(Builder $query, int $creatorId): Builder
110
    {
111
        return $query->where('creator_id', $creatorId);
112
    }
113
114
    /**
115
     * @param Builder $query
116
     * @param string $type
117
     * @return Builder
118
     */
119
    public function scopeWhereTypeIs(Builder $query, string $type): Builder
120
    {
121
        return $query->where('type', $type);
122
    }
123
124
    /**
125
     * @param Builder $query
126
     * @param string $flag
127
     * @return Builder
128
     */
129
    public function scopeWhereFlagIs(Builder $query, string $flag): Builder
130
    {
131
        return $query->where('flag', $flag);
132
    }
133
134
    /**
135
     * @param UploadedFile $uploadedFile
136
     * @param User|null $creator
137
     * @param HasFileInterface|null $owner
138
     * @param string|null $disk
139
     * @param string|null $flag
140
     * @return $this
141
     */
142
    public function upload(
143
        UploadedFile $uploadedFile,
144
        ?User $creator = null,
145
        ?HasFileInterface $owner = null,
146
        ?string $disk = 'public',
147
        ?string $flag = null
148
    ): self {
149
        $uuid = Str::uuid();
150
        $path = is_null($creator) ? $uuid : sprintf('%s/%s', $creator->getKey(), $uuid);
0 ignored issues
show
Bug introduced by
It seems like you code against a concrete implementation and not the interface Illuminate\Contracts\Auth\Authenticatable as the method getKey() does only exist in the following implementations of said interface: Illuminate\Foundation\Auth\User.

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...
151
        $path = $uploadedFile->store($path, $disk);
0 ignored issues
show
Bug introduced by
It seems like $path defined by $uploadedFile->store($path, $disk) on line 151 can also be of type object<Ramsey\Uuid\UuidInterface>; however, Illuminate\Http\UploadedFile::store() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
152
153
        $file = new self();
154
        $file->uuid = $uuid;
155
        $file->creator()->associate($creator);
0 ignored issues
show
Documentation introduced by
$creator is of type null|object<Illuminate\C...s\Auth\Authenticatable>, but the function expects a object<Illuminate\Databa...t\Model>|integer|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
156
        $file->owner()->associate($owner);
0 ignored issues
show
Documentation introduced by
$owner is of type object<Hamidrezaniazi\Up...\HasFileInterface>|null, but the function expects a object<Illuminate\Database\Eloquent\Model>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
157
        $file->filename = $uploadedFile->getClientOriginalName();
158
        $file->mime = $uploadedFile->getClientMimeType();
159
        $file->disk = $disk;
160
        $file->type = strtok($uploadedFile->getClientMimeType(), '/');
161
        $file->flag = $flag;
162
        $file->path = $path;
163
        $file->save();
164
165
        return $file;
166
    }
167
168
    /**
169
     * @return bool|null
170
     * @throws \Exception
171
     */
172
    public function delete()
173
    {
174
        Storage::disk($this->disk)->delete($this->path);
0 ignored issues
show
Security Bug introduced by
It seems like $this->path can also be of type false; however, Illuminate\Contracts\Fil...em\Filesystem::delete() does only seem to accept string|array, did you maybe forget to handle an error condition?
Loading history...
175
        return parent::delete();
176
    }
177
178
    /**
179
     * @return string
180
     */
181
    public function getUrlAttribute(): string
182
    {
183
        return Storage::disk($this->disk)->url($this->path);
184
    }
185
}
186