Issues (11)

src/data/MongoDB.php (1 issue)

Severity
1
<?php
2
3
namespace alkemann\h2l\data;
4
5
use alkemann\h2l\exceptions\ConnectionError;
6
use alkemann\h2l\interfaces\Source;
7
use alkemann\h2l\Log;
8
use Exception;
9
use Generator;
10
use MongoDB\BSON\ObjectId;
11
use MongoDB\Client;
12
use MongoDB\Collection;
13
use MongoDB\Driver\Exception\RuntimeException;
14
use MongoDB\Model\BSONDocument;
15
16
/**
17
 * Class MongoDb
18
 *
19
 * @package alkemann\h2l\data
20
 */
21
class MongoDB implements Source
22
{
23
    /**
24
     * @var string[]
25
     */
26
    public static array $operators = [
27
        '$all',
28
        '$gt',
29
        '$gte',
30
        '$in',
31
        '$lt',
32
        '$lte',
33
        '$ne',
34
        '$nin',
35
        '$or',
36
        '$and',
37
        '$not',
38
        '$nor',
39
        '$exists',
40
        '$mod',
41
        'type',
42
        '$elemMatch',
43
        '$size'
44
    ];
45
46
    /** @var array<string, mixed> */
47
    protected array $config = [];
48
    /** @var null|Client */
49
    protected $client = null;
50
51
    /**
52
     * @param array $config
53
     */
54
    public function __construct(array $config = [])
55
    {
56
        $defaults = [
57
            'host' => 'localhost',
58
            'port' => 27017,
59
            'db' => 'default',
60
            'check_connections' => false,
61
        ];
62
        $this->config = $config + $defaults;
63
    }
64
65
    /**
66
     * @param string $collection
67
     * @return Collection
68
     * @throws ConnectionError un unable to connect
69
     */
70
    private function collection(string $collection): Collection
71
    {
72
        $db = $this->config['db'];
73
        if ($this->client == null) {
74
            $host = $this->config['host'];
75
            $port = $this->config['port'];
76
            $options = $this->config;
77
            unset($options['host']);
78
            unset($options['port']);
79
            unset($options['db']);
80
            try {
81
                $this->client = new Client("mongodb://{$host}:{$port}/{$db}", $options);
82
                if ($this->config['check_connections'] === true) {
83
                    $this->client->selectDatabase($this->config['db'])->listCollections();
84
                }
85
            } catch (RuntimeException $e) {
86
                throw new ConnectionError("Unable to connect to {$host}:{$port} : " . $e->getMessage());
87
            }
88
        }
89
        return $this->client->selectCollection($db, $collection);
90
    }
91
92
    /**
93
     * @param string $id
94
     * @return ObjectId
95
     */
96
    public static function id(string $id): ObjectId
97
    {
98
        return new ObjectId($id);
99
    }
100
101
    /**
102
     * @param mixed $query
103
     * @param array $params
104
     * @throws Exception if called as not implemented
105
     */
106
    public function query($query, array $params = []): void
107
    {
108
        throw new Exception("Query method is not implemented for MongDB");
109
    }
110
111
    /**
112
     * @param string $collection_name
113
     * @param array $conditions
114
     * @param array $options
115
     * @return array|null
116
     */
117
    public function one(string $collection_name, array $conditions, array $options = []): ?array
118
    {
119
        $collection = $this->collection($collection_name);
120
        $conditions = $this->idReplaceConditions($conditions);
121
        $result = $collection->findOne($conditions, $options);
122
        if ($result === null) {
123
            return null;
124
        }
125
        return $result instanceof BSONDocument ? $this->out($result) : (array) $result;
126
    }
127
128
    /**
129
     * @param array $conditions
130
     * @return array
131
     */
132
    private function idReplaceConditions(array $conditions): array
133
    {
134
        if (array_key_exists('id', $conditions)) {
135
            $id = new ObjectId($conditions['id']);
136
            unset($conditions['id']);
137
            $conditions['_id'] = $id;
138
        }
139
        return $conditions;
140
    }
141
142
    /**
143
     * @TODO keep the BSON object?
144
     * @param array|BSONDocument $document
145
     * @return array
146
     */
147
    private function out(array|BSONDocument $document): array
148
    {
149
        if (is_array($document)) {
0 ignored issues
show
The condition is_array($document) is always true.
Loading history...
150
            if (isset($document['_id'])) {
151
                $document['id'] = $document['_id'];
152
                unset($document['_id']);
153
            }
154
            return $document;
155
        }
156
        $a = $document->getArrayCopy();
157
        if (isset($document->_id)) {
158
            $a['id'] = "{$document->_id}";
159
            unset($a['_id']);
160
        }
161
        return $a;
162
    }
163
164
    /**
165
     * @param string $collection_name
166
     * @param array $conditions
167
     * @param array $options
168
     * @return Generator
169
     */
170
    public function find(string $collection_name, array $conditions, array $options = []): Generator
171
    {
172
        $collection = $this->collection($collection_name);
173
        $conditions = $this->idReplaceConditions($conditions);
174
        $cursor = $collection->find($conditions, $options);
175
        foreach ($cursor as $document) {
176
            yield $this->out($document);
177
        }
178
    }
179
180
    /**
181
     * @param string $collection_name
182
     * @param array $conditions
183
     * @param array $data
184
     * @param array $options
185
     * @return int
186
     */
187
    public function update(string $collection_name, array $conditions, array $data, array $options = []): int
188
    {
189
        $collection = $this->collection($collection_name);
190
        $result = $collection->updateMany($conditions, $data, $options);
191
        if ($result->isAcknowledged() === false) {
192
            // Throw exception or error?
193
            return 0;
194
        }
195
        return $result->getModifiedCount() ?? 0;
196
    }
197
198
    /**
199
     * @param string $collection_name
200
     * @param array $data
201
     * @param array $options
202
     * @return null|ObjectId
203
     */
204
    public function insert(string $collection_name, array $data, array $options = []): ?ObjectId
205
    {
206
        $collection_name = $this->collection($collection_name);
207
        $result = $collection_name->insertOne($data, $options);
208
        if ($result->isAcknowledged() === false) {
209
            // Throw exception or error?
210
            return null;
211
        }
212
        if ($result->getInsertedCount() != 1) {
213
            Log::error("Failed to insert!");
214
            return null;
215
        }
216
        $id = $result->getInsertedId();
217
        return $id instanceof ObjectId ? $id : null;
218
    }
219
220
    /**
221
     * @param string $collection_name
222
     * @param array $conditions
223
     * @param array $options
224
     * @return int
225
     */
226
    public function delete(string $collection_name, array $conditions, array $options = []): int
227
    {
228
        $collection_name = $this->collection($collection_name);
229
        $result = $collection_name->deleteMany($conditions, $options);
230
        if ($result->isAcknowledged() === false) {
231
            // Throw exception or error?
232
            return 0;
233
        }
234
        return $result->getDeletedCount();
235
    }
236
}
237