Completed
Push — master ( 9dd383...b03b84 )
by Oliver
02:23
created

Metable::isDirty()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 1 Features 0
Metric Value
c 2
b 1
f 0
dl 0
loc 16
rs 9.2
nc 4
cc 4
eloc 8
nop 1
1
<?php
2
3
/*
4
 * This file is part of Mailable.
5
 *
6
 * (c) Oliver Green <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace BoxedCode\Eloquent\Meta;
13
14
use BoxedCode\Eloquent\Meta\Contracts\MetaItem;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, BoxedCode\Eloquent\Meta\MetaItem.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
15
16
trait Metable
17
{
18
    /**
19
     * Boot the trait.
20
     *
21
     * @return void
22
     */
23
    public static function bootMetable()
24
    {
25
        static::observeSaveAndCascade();
26
27
        static::observeDeleteAndCascade();
28
    }
29
30
    /**
31
     * Get the primary key for the model.
32
     *
33
     * @return string
34
     */
35
    abstract function getKeyName();
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
36
37
    /**
38
     * Meta item relationship.
39
     *
40
     * @return \BoxedCode\Eloquent\Meta\HasMeta
41
     */
42
    public function meta()
43
    {
44
        return $this->hasMeta($this->getMetaItemClassName(), 'model');
45
    }
46
47
    /**
48
     * Determine if the model or given attribute(s) have been modified.
49
     *
50
     * @param  array|string|null  $attributes
51
     * @return bool
52
     */
53
    public function isDirty($attributes = null)
54
    {
55
        if (parent::isDirty($attributes)) {
56
            return true;
57
        }
58
59
        else {
60
            foreach ($this->meta as $item) {
1 ignored issue
show
Bug introduced by
The property meta 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...
61
                if ($item->isDirty($attributes)) {
62
                    return true;
63
                }
64
            }
65
        }
66
67
        return false;
68
    }
69
70
    /**
71
     * Get the class name of meta relationship items.
72
     *
73
     * @return string
74
     */
75
    public function getMetaItemClassName()
76
    {
77
        if (isset($this->metaItemClassname)) {
78
            $class = $this->metaItemClassname;
1 ignored issue
show
Bug introduced by
The property metaItemClassname 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...
79
        } else {
80
            $class = MetaItem::class;
81
        }
82
83
        return $class;
84
    }
85
86
    /**
87
     * Get an instance of the meta relationship item class.
88
     *
89
     * @param array $attr
90
     * @return MetaItem
91
     */
92
    public function getMetaItemInstance($attr = [])
93
    {
94
        $class = $this->getMetaItemClassName();
95
96
        return new $class($attr);
97
    }
98
99
    /**
100
     * Get the polymorphic relationship columns.
101
     *
102
     * @param  string  $name
103
     * @param  string  $type
104
     * @param  string  $id
105
     * @return array
106
     */
107
    abstract function getMorphs($name, $type, $id);
108
109
    /**
110
     * Define the polymorphic one-to-many relationship with the meta data.
111
     *
112
     * @param  string  $related
113
     * @param  string  $name
114
     * @param  string  $type
115
     * @param  string  $id
116
     * @param  string  $localKey
117
     * @return \BoxedCode\Eloquent\Meta\HasMeta
118
     */
119
    public function hasMeta($related, $name, $type = null, $id = null, $localKey = null)
120
    {
121
        $instance = new $related;
122
123
        // Here we will gather up the morph type and ID for the relationship so that we
124
        // can properly query the intermediate table of a relation. Finally, we will
125
        // get the table and create the relationship instances for the developers.
126
        list($type, $id) = $this->getMorphs($name, $type, $id);
127
128
        $table = $instance->getTable();
129
130
        $localKey = $localKey ?: $this->getKeyName();
131
132
        return new HasMeta($instance->newQuery(), $this, $table.'.'.$type, $table.'.'.$id, $localKey);
133
    }
134
135
    /**
136
     * Observes the model and saves dirty meta data on save.
137
     *
138
     * @return void
139
     */
140
    public static function observeSaveAndCascade()
141
    {
142
        $onSave = function($model) {
143
144
            /*
145
             * Remove any keys not present in the collection
146
             */
147
            $key = $model->getMetaItemInstance()->getKeyName();
148
149
            $to_remove = array_diff($model->meta->originalModelKeys(), $model->meta->modelKeys());
150
151
            $model->meta()->whereIn($key, $to_remove)->delete();
152
153
            /*
154
             * Save dirty meta items
155
             */
156
            foreach ($model->meta as $meta) {
157
158
                if ($meta->isDirty()) {
159
                    if ($meta->exists) {
160
                        $meta->save();
161
                    } else {
162
                        $model->meta()->save($meta);
163
                    }
164
                }
165
166
            }
167
168
        };
169
170
        static::saved($onSave);
171
    }
172
173
    /**
174
     * Observes the model and deletes meta entries on delete.
175
     *
176
     * @return void
177
     */
178
    public static function observeDeleteAndCascade()
179
    {
180
        $onDelete = function($model) {
181
            foreach ($model->meta as $meta) {
182
                $meta->delete();
183
            }
184
        };
185
186
        static::deleted($onDelete);
187
    }
188
189
}
190