Completed
Branch feature/pre-split (cb15b4)
by Anton
03:23
created

Document   A

Complexity

Total Complexity 12

Size/Duplication

Total Lines 156
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 0
Metric Value
dl 0
loc 156
rs 10
c 0
b 0
f 0
wmc 12
lcom 1
cbo 5

6 Methods

Rating   Name   Duplication   Size   Complexity  
A isLoaded() 0 4 1
A __construct() 0 9 2
A primaryKey() 0 4 1
B save() 0 34 4
A delete() 0 16 2
A publicValue() 0 11 2
1
<?php
2
/**
3
 * Spiral Framework, Core Components
4
 *
5
 * @author    Wolfy-J
6
 */
7
namespace Spiral\ODM;
8
9
use MongoDB\BSON\ObjectID;
10
use Spiral\Models\ActiveEntityInterface;
11
use Spiral\ODM\Events\DocumentEvent;
12
13
/**
14
 * DocumentEntity with added ActiveRecord methods and ability to connect to associated source.
15
 * Document also provides an ability to specify aggregations using it's schema:
16
 *
17
 * const SCHEMA = [
18
 *     ...,
19
 *     'outer' => [
20
 *          self::ONE => Outer::class, [ //Reference to outer document using internal
21
 *              '_id' => 'self::outerID' //outerID value
22
 *          ]
23
 *      ],
24
 *     'many' => [
25
 *          self::MANY => Outer::class, [ //Reference to many outer document using
26
 *              'innerID' => 'self::_id'  //document primary key
27
 *          ]
28
 *     ]
29
 * ];
30
 *
31
 * Note: self::{name} construction will be replaced with document value in resulted query, even
32
 * in case of arrays ;) You can also use dot notation to get value from nested document.
33
 *
34
 * Attention, document will be linked to default database and named collection by default, use
35
 * properties database and collection to define your own custom database and collection.
36
 *
37
 * You can use property "index" to declare needed document indexes:
38
 *
39
 * Set of indexes to be created for associated collection. Use self::INDEX_OPTIONS or "@options"
40
 * for additional parameters.
41
 *
42
 * Example:
43
 * const INDEXES = [
44
 *      ['email' => 1, '@options' => ['unique' => true]],
45
 *      ['name' => 1]
46
 * ];
47
 *
48
 * @link http://php.net/manual/en/mongocollection.ensureindex.php
49
 *
50
 * Configuration properties:
51
 * - database
52
 * - collection
53
 * - schema
54
 * - indexes
55
 * - defaults
56
 * - secured (* by default)
57
 * - fillable
58
 */
59
abstract class Document extends DocumentEntity implements ActiveEntityInterface
60
{
61
    /**
62
     * Associated collection and database names, by default will be resolved based on a class name.
63
     */
64
    const DATABASE   = null;
65
    const COLLECTION = null;
66
67
    /**
68
     * When set to true publicFields() method (and jsonSerialize) will replace '_id' property with
69
     * 'id'.
70
     */
71
    const HIDE_UNDERSCORE_ID = true;
72
73
    /**
74
     * Set of indexes to be created for associated collection. Use "@options" for additional
75
     * index options.
76
     *
77
     * Example:
78
     * const INDEXES = [
79
     *      ['email' => 1, '@options' => ['unique' => true]],
80
     *      ['name' => 1]
81
     * ];
82
     *
83
     * @link http://php.net/manual/en/mongocollection.ensureindex.php
84
     * @var array
85
     */
86
    const INDEXES = [];
87
88
    /**
89
     * Documents must ALWAYS have _id field.
90
     */
91
    const SCHEMA = [
92
        '_id' => ObjectID::class
93
    ];
94
95
    /**
96
     * _id is always nullable.
97
     */
98
    const DEFAULTS = [
99
        '_id' => null
100
    ];
101
102
    /**
103
     * {@inheritdoc}
104
     */
105
    public function __construct(array $fields = [], ODMInterface $odm = null, array $schema = null)
106
    {
107
        parent::__construct($fields, $odm, $schema);
108
109
        if (!$this->isLoaded()) {
110
            //Automatically force solidState for newly created documents
111
            $this->solidState(true);
112
        }
113
    }
114
115
    /**
116
     * {@inheritdoc}
117
     */
118
    public function isLoaded(): bool
119
    {
120
        return !is_null($this->primaryKey());
121
    }
122
123
    /**
124
     * {@inheritdoc}
125
     */
126
    public function primaryKey()
127
    {
128
        return $this->getField('_id', null, false);
129
    }
130
131
    /**
132
     * {@inheritdoc}
133
     *
134
     * Check model setting HIDE_UNDERSCORE_ID in order to enable/disable automatic conversion of
135
     * '_id' to 'id'.
136
     */
137
    public function publicValue(): array
138
    {
139
        $public = parent::publicValue();
140
        if (static::HIDE_UNDERSCORE_ID) {
141
            //Replace '_id' property with 'id'
142
            unset($public['_id']);
143
            $public = ['id' => (string)$this->primaryKey()] + $public;
144
        }
145
146
        return $public;
147
    }
148
149
    /**
150
     * {@inheritdoc}
151
     *
152
     * @event create(DocumentEvent)
153
     * @event created(DocumentEvent)
154
     * @event update(DocumentEvent)
155
     * @event updated(DocumentEvent)
156
     */
157
    public function save(): int
158
    {
159
        if (!$this->isLoaded()) {
160
            $this->dispatch('create', new DocumentEvent($this));
161
162
            //Performing creation
163
            $result = $this->odm->collection(static::class)->insertOne($this->packValue(false));
164
            $this->setField('_id', $result->getInsertedId());
165
            $this->flushUpdates();
166
            //Done with creation
167
168
            $this->dispatch('created', new DocumentEvent($this));
169
170
            return self::CREATED;
171
        }
172
173
        if ($this->isSolid() || $this->hasUpdates()) {
174
            $this->dispatch('update', new DocumentEvent($this));
175
176
            //Performing an update
177
            $this->odm->collection(static::class)->updateOne(
178
                ['_id' => $this->primaryKey()],
179
                $this->buildAtomics()
180
            );
181
            $this->flushUpdates();
182
            //Done with update
183
184
            $this->dispatch('updated', new DocumentEvent($this));
185
186
            return self::UPDATED;
187
        }
188
189
        return self::UNCHANGED;
190
    }
191
192
    /**
193
     * {@inheritdoc}
194
     *
195
     * @event delete(DocumentEvent)
196
     * @event deleted(DocumentEvent)
197
     */
198
    public function delete()
199
    {
200
        if (!$this->isLoaded()) {
201
            //Nothing to do, do we need an exception here?
202
            return;
203
        }
204
205
        $this->dispatch('delete', new DocumentEvent($this));
206
207
        //Performing deletion
208
        $this->odm->collection(static::class)->deleteOne(['_id' => $this->primaryKey()]);
209
        $this->setField('_id', null, false);
210
        //Done with deletion
211
212
        $this->dispatch('deleted', new DocumentEvent($this));
213
    }
214
}