1 | <?php |
||||||
2 | |||||||
3 | namespace JPNut\Versioning; |
||||||
4 | |||||||
5 | use Exception; |
||||||
6 | use Illuminate\Database\Eloquent\Builder; |
||||||
7 | use Illuminate\Database\Eloquent\Relations\HasMany; |
||||||
8 | use Illuminate\Support\Str; |
||||||
9 | |||||||
10 | trait VersionableTrait |
||||||
11 | { |
||||||
12 | /** |
||||||
13 | * @return void |
||||||
14 | */ |
||||||
15 | public static function bootVersionableTrait() |
||||||
16 | { |
||||||
17 | static::addGlobalScope(new VersioningScope()); |
||||||
18 | } |
||||||
19 | |||||||
20 | /** |
||||||
21 | * Get a new query builder that doesn't have any global scopes or eager loading. |
||||||
22 | * |
||||||
23 | * @return \Illuminate\Database\Eloquent\Builder|static |
||||||
24 | */ |
||||||
25 | public function newModelQuery() |
||||||
26 | { |
||||||
27 | if (!$this->hasVersionJoin($builder = parent::newModelQuery(), $this->getVersionTable())) { |
||||||
28 | $builder |
||||||
29 | ->join( |
||||||
30 | $this->getVersionTable(), |
||||||
31 | function ($join) { |
||||||
32 | $join->on($this->getQualifiedKeyName(), '=', $this->getQualifiedVersionTableForeignKeyName()) |
||||||
0 ignored issues
–
show
|
|||||||
33 | ->on($this->getQualifiedVersionTableKeyName(), '=', $this->getQualifiedVersionKeyName()); |
||||||
34 | } |
||||||
35 | ) |
||||||
36 | ->select( |
||||||
37 | $this->defaultVersionSelect() |
||||||
38 | ); |
||||||
39 | } |
||||||
40 | |||||||
41 | return $builder; |
||||||
42 | } |
||||||
43 | |||||||
44 | /** |
||||||
45 | * @return array|string[] |
||||||
46 | */ |
||||||
47 | public function defaultVersionSelect(): array |
||||||
48 | { |
||||||
49 | return array_merge( |
||||||
50 | [ |
||||||
51 | $this->getTable().'.*', |
||||||
0 ignored issues
–
show
It seems like
getTable() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
52 | $this->getQualifiedVersionTableKeyName(), |
||||||
53 | ], |
||||||
54 | $this->getQualifiedVersionableAttributes(), |
||||||
55 | ); |
||||||
56 | } |
||||||
57 | |||||||
58 | /** |
||||||
59 | * Determine if the given builder contains a join with the given table. |
||||||
60 | * |
||||||
61 | * @param \Illuminate\Database\Eloquent\Builder $builder |
||||||
62 | * @param string $table |
||||||
63 | * |
||||||
64 | * @return bool |
||||||
65 | */ |
||||||
66 | protected function hasVersionJoin(Builder $builder, string $table) |
||||||
67 | { |
||||||
68 | return collect($builder->getQuery()->joins)->pluck('table')->contains($table); |
||||||
69 | } |
||||||
70 | |||||||
71 | /** |
||||||
72 | * Perform a model insert operation. |
||||||
73 | * |
||||||
74 | * @param \Illuminate\Database\Eloquent\Builder $query |
||||||
75 | * |
||||||
76 | * @return bool |
||||||
77 | */ |
||||||
78 | protected function performInsert(Builder $query) |
||||||
79 | { |
||||||
80 | if ($this->fireModelEvent('creating') === false) { |
||||||
0 ignored issues
–
show
It seems like
fireModelEvent() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
81 | return false; |
||||||
82 | } |
||||||
83 | |||||||
84 | // First we'll need to create a fresh query instance and touch the creation and |
||||||
85 | // update timestamps on this model, which are maintained by us for developer |
||||||
86 | // convenience. After, we will just continue saving these model instances. |
||||||
87 | if ($this->usesTimestamps()) { |
||||||
0 ignored issues
–
show
It seems like
usesTimestamps() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
88 | $this->updateTimestamps(); |
||||||
0 ignored issues
–
show
It seems like
updateTimestamps() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
89 | } |
||||||
90 | |||||||
91 | // Ensure that the initial version number is set to 1. This sets the attribute, |
||||||
92 | // and will be synced into storage later. |
||||||
93 | $this->setVersionKey(1); |
||||||
94 | |||||||
95 | // If the model has an incrementing key, we can use the "insertGetId" method on |
||||||
96 | // the query builder, which will give us back the final inserted ID for this |
||||||
97 | // table from the database. Not all tables have to be incrementing though. |
||||||
98 | $attributes = $this->getAttributes(); |
||||||
0 ignored issues
–
show
It seems like
getAttributes() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
99 | |||||||
100 | // get version values & master attributes |
||||||
101 | $versionAttributes = $this->getVersionAttributes($attributes); |
||||||
102 | $masterAttributes = array_diff_key($attributes, $versionAttributes); |
||||||
103 | |||||||
104 | if ($this->getIncrementing()) { |
||||||
0 ignored issues
–
show
It seems like
getIncrementing() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
105 | $this->insertAndSetId($query, $masterAttributes); |
||||||
0 ignored issues
–
show
It seems like
insertAndSetId() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
106 | } |
||||||
107 | |||||||
108 | // If the table isn't incrementing we'll simply insert these attributes as they |
||||||
109 | // are. These attribute arrays must contain an "id" column previously placed |
||||||
110 | // there by the developer as the manually determined key for these models. |
||||||
111 | else { |
||||||
112 | if (empty($masterAttributes)) { |
||||||
113 | return true; |
||||||
114 | } |
||||||
115 | |||||||
116 | $query->insert($masterAttributes); |
||||||
117 | } |
||||||
118 | |||||||
119 | // insert the initial version into the version table |
||||||
120 | $this->insertVersion($versionAttributes); |
||||||
121 | |||||||
122 | // We will go ahead and set the exists property to true, so that it is set when |
||||||
123 | // the created event is fired, just in case the developer tries to update it |
||||||
124 | // during the event. This will allow them to do so and run an update here. |
||||||
125 | $this->exists = true; |
||||||
0 ignored issues
–
show
|
|||||||
126 | |||||||
127 | $this->wasRecentlyCreated = true; |
||||||
0 ignored issues
–
show
|
|||||||
128 | |||||||
129 | $this->fireModelEvent('created', false); |
||||||
130 | |||||||
131 | return true; |
||||||
132 | } |
||||||
133 | |||||||
134 | /** |
||||||
135 | * Perform a model update operation. |
||||||
136 | * |
||||||
137 | * @param \Illuminate\Database\Eloquent\Builder $query |
||||||
138 | * |
||||||
139 | * @return bool |
||||||
140 | */ |
||||||
141 | protected function performUpdate(Builder $query) |
||||||
142 | { |
||||||
143 | // If the updating event returns false, we will cancel the update operation so |
||||||
144 | // developers can hook Validation systems into their models and cancel this |
||||||
145 | // operation if the model does not pass validation. Otherwise, we update. |
||||||
146 | if ($this->fireModelEvent('updating') === false) { |
||||||
147 | return false; |
||||||
148 | } |
||||||
149 | |||||||
150 | // First we need to create a fresh query instance and touch the creation and |
||||||
151 | // update timestamp on the model which are maintained by us for developer |
||||||
152 | // convenience. Then we will just continue saving the model instances. |
||||||
153 | if ($this->usesTimestamps()) { |
||||||
154 | $this->updateTimestamps(); |
||||||
155 | } |
||||||
156 | |||||||
157 | // Once we have run the update operation, we will fire the "updated" event for |
||||||
158 | // this model instance. This will allow developers to hook into these after |
||||||
159 | // models are updated, giving them a chance to do any special processing. |
||||||
160 | $dirty = $this->getDirty(); |
||||||
0 ignored issues
–
show
It seems like
getDirty() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
161 | |||||||
162 | if (count($dirty) === 0) { |
||||||
163 | return true; |
||||||
164 | } |
||||||
165 | |||||||
166 | // get version values & master attributes |
||||||
167 | $versionAttributes = $this->getVersionAttributes($dirty); |
||||||
168 | $masterAttributes = array_diff_key($dirty, $versionAttributes); |
||||||
169 | |||||||
170 | $shouldCreateNewVersion = $this->shouldCreateNewVersion($versionAttributes); |
||||||
171 | |||||||
172 | $this->setKeysForSaveQuery($query) |
||||||
0 ignored issues
–
show
It seems like
setKeysForSaveQuery() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
173 | ->increment( |
||||||
174 | $this->getVersionKeyName(), |
||||||
175 | $shouldCreateNewVersion ? 1 : 0, |
||||||
176 | $masterAttributes |
||||||
177 | ); |
||||||
178 | |||||||
179 | // If we need to create a new version, we first of all must manually increment |
||||||
180 | // the version counter. This ensures the value is synced with the database. |
||||||
181 | // We also use this value to insert a new version to the versions table. |
||||||
182 | if ($shouldCreateNewVersion) { |
||||||
183 | $this->setVersionKey($this->getVersionKey() + 1); |
||||||
184 | |||||||
185 | $this->insertVersion( |
||||||
186 | array_merge( |
||||||
187 | $this->getVersionAttributes($this->getAttributes()), |
||||||
188 | $versionAttributes |
||||||
189 | ) |
||||||
190 | ); |
||||||
191 | } |
||||||
192 | |||||||
193 | $this->syncChanges(); |
||||||
0 ignored issues
–
show
It seems like
syncChanges() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
194 | |||||||
195 | $this->fireModelEvent('updated', false); |
||||||
196 | |||||||
197 | return true; |
||||||
198 | } |
||||||
199 | |||||||
200 | /** |
||||||
201 | * Delete the model from the database. |
||||||
202 | * |
||||||
203 | * @throws \Exception |
||||||
204 | * |
||||||
205 | * @return bool|null |
||||||
206 | */ |
||||||
207 | public function delete() |
||||||
208 | { |
||||||
209 | if (is_null($this->getKeyName())) { |
||||||
0 ignored issues
–
show
It seems like
getKeyName() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
210 | throw new Exception('No primary key defined on model.'); |
||||||
211 | } |
||||||
212 | |||||||
213 | // If the model doesn't exist, there is nothing to delete so we'll just return |
||||||
214 | // immediately and not do anything else. Otherwise, we will continue with a |
||||||
215 | // deletion process on the model, firing the proper events, and so forth. |
||||||
216 | if (!$this->exists) { |
||||||
217 | return null; |
||||||
218 | } |
||||||
219 | |||||||
220 | if ($this->fireModelEvent('deleting') === false) { |
||||||
221 | return false; |
||||||
222 | } |
||||||
223 | |||||||
224 | // Here, we'll touch the owning models, verifying these timestamps get updated |
||||||
225 | // for the models. This will allow any caching to get broken on the parents |
||||||
226 | // by the timestamp. Then we will go ahead and delete the model instance. |
||||||
227 | $this->touchOwners(); |
||||||
0 ignored issues
–
show
It seems like
touchOwners() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
228 | |||||||
229 | $this->performDeleteOnModel(); |
||||||
0 ignored issues
–
show
It seems like
performDeleteOnModel() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
230 | |||||||
231 | // We'd like to remove all the versions associated with this model, but we need |
||||||
232 | // to make sure the model wasn't soft deleted first. |
||||||
233 | if (!$this->exists) { |
||||||
234 | $this->versions()->delete(); |
||||||
235 | } |
||||||
236 | |||||||
237 | // Once the model has been deleted, we will fire off the deleted event so that |
||||||
238 | // the developers may hook into post-delete operations. We will then return |
||||||
239 | // a boolean true as the delete is presumably successful on the database. |
||||||
240 | $this->fireModelEvent('deleted', false); |
||||||
241 | |||||||
242 | return true; |
||||||
243 | } |
||||||
244 | |||||||
245 | /** |
||||||
246 | * @param array $attributes |
||||||
247 | * |
||||||
248 | * @return bool |
||||||
249 | */ |
||||||
250 | protected function shouldCreateNewVersion(array $attributes): bool |
||||||
251 | { |
||||||
252 | return !empty($attributes); |
||||||
253 | } |
||||||
254 | |||||||
255 | /** |
||||||
256 | * @param array $attributes |
||||||
257 | * |
||||||
258 | * @return mixed |
||||||
259 | */ |
||||||
260 | protected function insertVersion(array $attributes) |
||||||
261 | { |
||||||
262 | return $this->versions() |
||||||
263 | ->newModelInstance() |
||||||
264 | ->forceFill( |
||||||
265 | array_merge( |
||||||
266 | $attributes, |
||||||
267 | [ |
||||||
268 | $this->getVersionTableForeignKeyName() => $this->getKey(), |
||||||
0 ignored issues
–
show
It seems like
getKey() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
269 | $this->getVersionTableKeyName() => $this->getVersionKey(), |
||||||
270 | $this->getVersionTableCreatedAtName() => $this->freshTimestampString(), |
||||||
0 ignored issues
–
show
It seems like
freshTimestampString() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
271 | ] |
||||||
272 | ) |
||||||
273 | ) |
||||||
274 | ->save(); |
||||||
275 | } |
||||||
276 | |||||||
277 | /** |
||||||
278 | * @param array $attributes |
||||||
279 | * |
||||||
280 | * @return array |
||||||
281 | */ |
||||||
282 | protected function getVersionAttributes(array $attributes) |
||||||
283 | { |
||||||
284 | $array = []; |
||||||
285 | |||||||
286 | $versionableAttributes = $this->getVersionableAttributes(); |
||||||
287 | |||||||
288 | foreach ($attributes as $key => $value) { |
||||||
289 | if ($newKey = $this->isVersionedKey($key, $versionableAttributes)) { |
||||||
290 | $array[$newKey] = $value; |
||||||
291 | } |
||||||
292 | } |
||||||
293 | |||||||
294 | return $array; |
||||||
295 | } |
||||||
296 | |||||||
297 | /** |
||||||
298 | * Check if key is in versioned keys. |
||||||
299 | * |
||||||
300 | * @param string $key |
||||||
301 | * @param array $versionedKeys |
||||||
302 | * |
||||||
303 | * @return string|null |
||||||
304 | */ |
||||||
305 | protected function isVersionedKey($key, array $versionedKeys) |
||||||
306 | { |
||||||
307 | return in_array($key, $versionedKeys) |
||||||
308 | ? $key |
||||||
309 | : null; |
||||||
310 | } |
||||||
311 | |||||||
312 | /** |
||||||
313 | * @return VersionOptions |
||||||
314 | */ |
||||||
315 | public function getVersionableOptions(): VersionOptions |
||||||
316 | { |
||||||
317 | return VersionOptions::create(); |
||||||
318 | } |
||||||
319 | |||||||
320 | /** |
||||||
321 | * @return string |
||||||
322 | */ |
||||||
323 | public function getVersionKeyName(): string |
||||||
324 | { |
||||||
325 | return $this->getVersionableOptions()->versionKeyName; |
||||||
326 | } |
||||||
327 | |||||||
328 | /** |
||||||
329 | * @return string |
||||||
330 | */ |
||||||
331 | public function getQualifiedVersionKeyName(): string |
||||||
332 | { |
||||||
333 | return $this->getTable().'.'.$this->getVersionKeyName(); |
||||||
334 | } |
||||||
335 | |||||||
336 | /** |
||||||
337 | * @return int |
||||||
338 | */ |
||||||
339 | public function getVersionKey(): int |
||||||
340 | { |
||||||
341 | return $this->getAttributeValue($this->getVersionKeyName()); |
||||||
0 ignored issues
–
show
It seems like
getAttributeValue() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
342 | } |
||||||
343 | |||||||
344 | /** |
||||||
345 | * @param int $version |
||||||
346 | * |
||||||
347 | * @return mixed |
||||||
348 | */ |
||||||
349 | public function setVersionKey(int $version) |
||||||
350 | { |
||||||
351 | return $this->setAttribute($this->getVersionKeyName(), $version); |
||||||
0 ignored issues
–
show
It seems like
setAttribute() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
352 | } |
||||||
353 | |||||||
354 | /** |
||||||
355 | * @return string |
||||||
356 | */ |
||||||
357 | public function getVersionTable(): string |
||||||
358 | { |
||||||
359 | return $this->getVersionableOptions()->versionTableName ?? Str::singular($this->getTable()).'_versions'; |
||||||
360 | } |
||||||
361 | |||||||
362 | /** |
||||||
363 | * @return string |
||||||
364 | */ |
||||||
365 | public function getVersionTableKeyName(): string |
||||||
366 | { |
||||||
367 | return $this->getVersionableOptions()->versionTableKeyName; |
||||||
368 | } |
||||||
369 | |||||||
370 | /** |
||||||
371 | * @return string |
||||||
372 | */ |
||||||
373 | public function getQualifiedVersionTableKeyName(): string |
||||||
374 | { |
||||||
375 | return $this->getVersionTable().'.'.$this->getVersionTableKeyName(); |
||||||
376 | } |
||||||
377 | |||||||
378 | /** |
||||||
379 | * @return string |
||||||
380 | */ |
||||||
381 | public function getVersionTableForeignKeyName(): string |
||||||
382 | { |
||||||
383 | return $this->getVersionableOptions()->versionTableForeignKeyName; |
||||||
384 | } |
||||||
385 | |||||||
386 | /** |
||||||
387 | * @return string |
||||||
388 | */ |
||||||
389 | public function getQualifiedVersionTableForeignKeyName(): string |
||||||
390 | { |
||||||
391 | return $this->getVersionTable().'.'.$this->getVersionTableForeignKeyName(); |
||||||
392 | } |
||||||
393 | |||||||
394 | /** |
||||||
395 | * @return string|null |
||||||
396 | */ |
||||||
397 | public function getVersionTableCreatedAtName(): string |
||||||
398 | { |
||||||
399 | return $this->getVersionableOptions()->versionTableCreatedAtName; |
||||||
400 | } |
||||||
401 | |||||||
402 | /** |
||||||
403 | * @return string |
||||||
404 | */ |
||||||
405 | public function getQualifiedVersionTableCreatedAtName(): string |
||||||
406 | { |
||||||
407 | return $this->getVersionTable().'.'.$this->getVersionTableCreatedAtName(); |
||||||
408 | } |
||||||
409 | |||||||
410 | /** |
||||||
411 | * @return array |
||||||
412 | */ |
||||||
413 | public function getVersionableAttributes(): array |
||||||
414 | { |
||||||
415 | return $this->getVersionableOptions()->versionableAttributes; |
||||||
416 | } |
||||||
417 | |||||||
418 | /** |
||||||
419 | * @return array |
||||||
420 | */ |
||||||
421 | public function getQualifiedVersionableAttributes(): array |
||||||
422 | { |
||||||
423 | return array_map( |
||||||
424 | function (string $attribute) { |
||||||
425 | return $this->getVersionTable().'.'.$attribute; |
||||||
426 | }, $this->getVersionableOptions()->versionableAttributes |
||||||
427 | ); |
||||||
428 | } |
||||||
429 | |||||||
430 | /** |
||||||
431 | * @return \Illuminate\Database\Eloquent\Relations\HasMany |
||||||
432 | */ |
||||||
433 | public function versions(): HasMany |
||||||
434 | { |
||||||
435 | return $this->newHasMany( |
||||||
0 ignored issues
–
show
It seems like
newHasMany() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
436 | $this->newRelatedInstance(Version::class) |
||||||
0 ignored issues
–
show
It seems like
newRelatedInstance() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
437 | ->setTable($this->getVersionTable()) |
||||||
438 | ->newQuery(), |
||||||
439 | $this, |
||||||
440 | $this->getQualifiedVersionTableForeignKeyName(), |
||||||
441 | $this->getKeyName() |
||||||
442 | ); |
||||||
443 | } |
||||||
444 | |||||||
445 | /** |
||||||
446 | * @param mixed $version |
||||||
447 | * |
||||||
448 | * @return \Illuminate\Database\Eloquent\Model |
||||||
449 | */ |
||||||
450 | public function version($version) |
||||||
451 | { |
||||||
452 | if ($version instanceof Version) { |
||||||
453 | return $version; |
||||||
454 | } |
||||||
455 | |||||||
456 | return $this->versions() |
||||||
457 | ->whereKey($version) |
||||||
458 | ->first(); |
||||||
459 | } |
||||||
460 | |||||||
461 | /** |
||||||
462 | * @param $version |
||||||
463 | * |
||||||
464 | * @return mixed |
||||||
465 | */ |
||||||
466 | public function changeVersion($version) |
||||||
467 | { |
||||||
468 | if ($this->wasRecentlyCreated) { |
||||||
469 | $this->refresh(); |
||||||
0 ignored issues
–
show
It seems like
refresh() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
470 | } |
||||||
471 | |||||||
472 | $this->fill( |
||||||
0 ignored issues
–
show
It seems like
fill() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
473 | $this->getVersionAttributes( |
||||||
474 | $this->version($version)->getAttributes() |
||||||
475 | ) |
||||||
476 | ); |
||||||
477 | |||||||
478 | $this->save(); |
||||||
0 ignored issues
–
show
It seems like
save() must be provided by classes using this trait. How about adding it as abstract method to this trait?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||||
479 | |||||||
480 | return $this; |
||||||
481 | } |
||||||
482 | } |
||||||
483 |
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.