Completed
Branch master (49dc1a)
by Yaro
01:42
created

VersionableTrait::getVersionModel()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.9666
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
namespace Yaro\Jarboe\Models\Traits;
4
5
use Illuminate\Database\Eloquent\Relations\MorphMany;
6
use Yaro\Jarboe\Models\Version;
7
8
trait VersionableTrait
9
{
10
    /**
11
     * Retrieve, if exists, the property that define that Version model.
12
     * If no property defined, use the default Version model.
13
     *
14
     * Trait cannot share properties whth their class !
15
     * http://php.net/manual/en/language.oop5.traits.php
16
     * @return unknown|string
17
     */
18
    protected function getVersionClass()
19
    {
20
        if (property_exists(self::class, 'versionClass')) {
21
            return $this->versionClass;
0 ignored issues
show
Bug introduced by
The property versionClass does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
22
        }
23
24
        return config('jarboe.versionable.version_model', Version::class);
25
    }
26
27
    /**
28
     * Private variable to detect if this is an update
29
     * or an insert
30
     * @var bool
31
     */
32
    private $updating;
33
34
    /**
35
     * Contains all dirty data that is valid for versioning
36
     *
37
     * @var array
38
     */
39
    private $versionableDirtyData;
40
41
    /**
42
     * Optional reason, why this version was created
43
     * @var string
44
     */
45
    private $reason;
46
47
    /**
48
     * Flag that determines if the model allows versioning at all
49
     * @var bool
50
     */
51
    protected $versioningEnabled = true;
52
53
    /**
54
     * @return $this
55
     */
56
    public function enableVersioning()
57
    {
58
        $this->versioningEnabled = true;
59
60
        return $this;
61
    }
62
63
    /**
64
     * @return $this
65
     */
66
    public function disableVersioning()
67
    {
68
        $this->versioningEnabled = false;
69
70
        return $this;
71
    }
72
73
    /**
74
     * Attribute mutator for "reason"
75
     * Prevent "reason" to become a database attribute of model
76
     *
77
     * @param string $value
78
     */
79
    public function setReasonAttribute($value)
80
    {
81
        $this->reason = $value;
82
    }
83
84
    /**
85
     * Initialize model events
86
     */
87
    public static function bootVersionableTrait()
88
    {
89
        static::saving(function ($model) {
90
            $model->versionablePreSave();
91
        });
92
93
        static::saved(function ($model) {
94
            $model->versionablePostSave();
95
        });
96
    }
97
98
    /**
99
     * Return all versions of the model
100
     * @return MorphMany
101
     */
102
    public function versions()
103
    {
104
        return $this->morphMany($this->getVersionClass(), 'versionable');
0 ignored issues
show
Bug introduced by
It seems like morphMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
105
    }
106
107
    /**
108
     * Returns the latest version available
109
     * @return Version
110
     */
111
    public function currentVersion()
112
    {
113
        $class = $this->getVersionClass();
114
        return $this->versions()->orderBy($class::CREATED_AT, 'DESC')->first();
115
    }
116
117
    /**
118
     * Returns the previous version
119
     * @return Version
120
     */
121
    public function previousVersion()
122
    {
123
        return $this->versions()->latest()->limit(1)->offset(1)->first();
124
    }
125
126
    /**
127
     * Get a model based on the version id
128
     *
129
     * @param $version_id
130
     *
131
     * @return $this|null
132
     */
133
    public function getVersionModel($version_id)
134
    {
135
        $version = $this->versions()->where('version_id', '=', $version_id)->first();
136
        if (!is_null($version)) {
137
            return $version->getModel();
138
        }
139
140
        return null;
141
    }
142
143
    /**
144
     * Pre save hook to determine if versioning is enabled and if we're updating
145
     * the model
146
     * @return void
147
     */
148
    protected function versionablePreSave()
149
    {
150
        if ($this->versioningEnabled === true) {
151
            $this->versionableDirtyData = $this->getDirty();
0 ignored issues
show
Bug introduced by
It seems like getDirty() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
152
            $this->updating = $this->exists;
0 ignored issues
show
Bug introduced by
The property exists does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
153
        }
154
    }
155
156
    /**
157
     * Save a new version.
158
     * @return void
159
     */
160
    protected function versionablePostSave()
161
    {
162
        /**
163
         * We'll save new versions on updating and first creation
164
         */
165
        if (
166
            ($this->versioningEnabled === true && $this->updating && $this->isValidForVersioning())
167
            || ($this->versioningEnabled === true && !$this->updating && !is_null($this->versionableDirtyData) && count($this->versionableDirtyData))
168
        ) {
169
            // Save a new version
170
            $class = $this->getVersionClass();
171
            $version = new $class();
172
            $version->versionable_id = $this->getKey();
0 ignored issues
show
Bug introduced by
It seems like getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
173
            $version->versionable_type = get_class($this);
174
            $version->user_id = $this->getAuthUserId();
175
            $version->auth_guard = $this->getAuthGuard();
176
            // https://github.com/mpociot/versionable/issues/43
177
            $version->model_data = serialize($this->attributesToArray());
0 ignored issues
show
Bug introduced by
It seems like attributesToArray() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
178
179
            if (!empty($this->reason)) {
180
                $version->reason = $this->reason;
181
            }
182
183
            $version->save();
184
185
            $this->purgeOldVersions();
186
        }
187
    }
188
189
    /**
190
     * Delete old versions of this model when the reach a specific count.
191
     *
192
     * @return void
193
     */
194
    private function purgeOldVersions()
195
    {
196
        $keep = isset($this->keepOldVersions) ? $this->keepOldVersions : 0;
0 ignored issues
show
Bug introduced by
The property keepOldVersions does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
197
        $count = $this->versions()->count();
198
199
        if ((int)$keep > 0 && $count > $keep) {
200
            $this->versions()
201
                ->latest()
202
                ->take($count)
203
                ->skip($keep)
204
                ->get()
205
                ->each(function ($version) {
206
                    $version->delete();
207
                });
208
        }
209
    }
210
211
    /**
212
     * Determine if a new version should be created for this model.
213
     *
214
     * @return bool
215
     */
216
    private function isValidForVersioning()
217
    {
218
        $dontVersionFields = isset($this->dontVersionFields) ? $this->dontVersionFields : [];
0 ignored issues
show
Bug introduced by
The property dontVersionFields does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
219
        $removeableKeys = array_merge($dontVersionFields, [$this->getUpdatedAtColumn()]);
0 ignored issues
show
Bug introduced by
It seems like getUpdatedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
220
221
        if (method_exists($this, 'getDeletedAtColumn')) {
222
            $removeableKeys[] = $this->getDeletedAtColumn();
0 ignored issues
show
Bug introduced by
It seems like getDeletedAtColumn() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
223
        }
224
225
        return (count(array_diff_key($this->versionableDirtyData, array_flip($removeableKeys))) > 0);
226
    }
227
228
    /**
229
     * @return int|null
230
     */
231
    protected function getAuthUserId()
232
    {
233
        $auth = auth();
234
        if ($auth->check()) {
0 ignored issues
show
Bug introduced by
The method check does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
235
            return $auth->id();
0 ignored issues
show
Bug introduced by
The method id does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
236
        }
237
238
        return null;
239
    }
240
241
    /**
242
     * @return string|null
243
     */
244
    protected function getAuthGuard()
245
    {
246
        $auth = auth();
247
        if ($auth->check()) {
0 ignored issues
show
Bug introduced by
The method check does only exist in Illuminate\Contracts\Auth\Guard, but not in Illuminate\Contracts\Auth\Factory.

It seems like the method you are trying to call exists only in some of the possible types.

Let’s take a look at an example:

class A
{
    public function foo() { }
}

class B extends A
{
    public function bar() { }
}

/**
 * @param A|B $x
 */
function someFunction($x)
{
    $x->foo(); // This call is fine as the method exists in A and B.
    $x->bar(); // This method only exists in B and might cause an error.
}

Available Fixes

  1. Add an additional type-check:

    /**
     * @param A|B $x
     */
    function someFunction($x)
    {
        $x->foo();
    
        if ($x instanceof B) {
            $x->bar();
        }
    }
    
  2. Only allow a single type to be passed if the variable comes from a parameter:

    function someFunction(B $x) { /** ... */ }
    
Loading history...
248
            return $auth->getDefaultDriver();
249
        }
250
251
        return null;
252
    }
253
}
254