1 /*
2 * Copyright 2014-2017 MongoDB, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <php.h>
18 #include <Zend/zend_hash.h>
19 #include <Zend/zend_interfaces.h>
20 #include <ext/standard/file.h>
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "php_array_api.h"
27 #include "phongo_compat.h"
28 #include "php_phongo.h"
29 #include "Session.h"
30
31 #define PHONGO_MANAGER_URI_DEFAULT "mongodb://127.0.0.1/"
32
33 /**
34 * Manager abstracts a cluster of Server objects (i.e. socket connections).
35 *
36 * Typically, users will connect to a cluster using a URI, and the Manager will
37 * perform tasks such as replica set discovery and create the necessary Server
38 * objects. That said, it is also possible to create a Manager with an arbitrary
39 * collection of Server objects using the static factory method (this can be
40 * useful for testing or administration).
41 *
42 * Operation methods do not take socket-level options (e.g. socketTimeoutMS).
43 * Those options should be specified during construction.
44 */
45 zend_class_entry* php_phongo_manager_ce;
46
47 /* Checks if driverOptions contains a stream context resource in the "context"
48 * key and incorporates any of its SSL options into the base array that did not
49 * already exist (i.e. array union). The "context" key is then unset from the
50 * base array.
51 *
52 * This handles the merging of any legacy SSL context options and also makes
53 * driverOptions suitable for serialization by removing the resource zval. */
php_phongo_manager_merge_context_options(zval * zdriverOptions)54 static bool php_phongo_manager_merge_context_options(zval* zdriverOptions) /* {{{ */
55 {
56 php_stream_context* context;
57 zval * zcontext, *zcontextOptions;
58
59 if (!php_array_existsc(zdriverOptions, "context")) {
60 return true;
61 }
62
63 zcontext = php_array_fetchc(zdriverOptions, "context");
64 context = php_stream_context_from_zval(zcontext, 1);
65
66 if (!context) {
67 phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "\"context\" driver option is not a valid Stream-Context resource");
68 return false;
69 }
70
71 zcontextOptions = php_array_fetchc_array(&context->options, "ssl");
72
73 if (!zcontextOptions) {
74 phongo_throw_exception(PHONGO_ERROR_INVALID_ARGUMENT, "Stream-Context resource does not contain \"ssl\" options array");
75 return false;
76 }
77
78 /* When running PHP in debug mode, php_error_docref duplicates the current
79 * scope, leading to a COW violation in zend_hash_merge and
80 * zend_symtable_str_del (called by php_array_unsetc). This macro allows
81 * that violation in debug mode and is a NOOP when in non-debug. */
82 #if PHP_VERSION_ID >= 70200
83 HT_ALLOW_COW_VIOLATION(Z_ARRVAL_P(zdriverOptions));
84 #endif
85
86 php_error_docref(NULL, E_DEPRECATED, "The \"context\" driver option is deprecated.");
87
88 /* Perform array union (see: add_function() in zend_operators.c) */
89 zend_hash_merge(Z_ARRVAL_P(zdriverOptions), Z_ARRVAL_P(zcontextOptions), zval_add_ref, 0);
90
91 php_array_unsetc(zdriverOptions, "context");
92
93 return true;
94 } /* }}} */
95
96 /* Prepare authMechanismProperties for BSON encoding by converting a boolean
97 * value for the "CANONICALIZE_HOST_NAME" option to a string.
98 *
99 * Note: URI options are case-insensitive, so we must iterate through the
100 * HashTable in order to detect options. */
php_phongo_manager_prep_authmechanismproperties(zval * properties)101 static void php_phongo_manager_prep_authmechanismproperties(zval* properties) /* {{{ */
102 {
103 HashTable* ht_data;
104
105 if (Z_TYPE_P(properties) != IS_ARRAY && Z_TYPE_P(properties) != IS_OBJECT) {
106 return;
107 }
108
109 ht_data = HASH_OF(properties);
110
111 {
112 zend_string* string_key = NULL;
113 zend_ulong num_key = 0;
114 zval* property;
115
116 ZEND_HASH_FOREACH_KEY_VAL_IND(ht_data, num_key, string_key, property)
117 {
118 if (!string_key) {
119 continue;
120 }
121
122 /* URI options are case-insensitive */
123 if (!strcasecmp(ZSTR_VAL(string_key), "CANONICALIZE_HOST_NAME")) {
124 ZVAL_DEREF(property);
125 if (Z_TYPE_P(property) != IS_STRING && zend_is_true(property)) {
126 SEPARATE_ZVAL_NOREF(property);
127 ZVAL_NEW_STR(property, zend_string_init(ZEND_STRL("true"), 0));
128 }
129 }
130 }
131 ZEND_HASH_FOREACH_END();
132 }
133 } /* }}} */
134
135 /* Prepare URI options for BSON encoding.
136 *
137 * Read preference tag sets must be an array of documents. In order to ensure
138 * that empty arrays serialize as empty documents, array elements will be
139 * converted to objects. php_phongo_read_preference_tags_are_valid() handles
140 * actual validation of the tag set structure.
141 *
142 * Auth mechanism properties must have string values, so a boolean true value
143 * for the "CANONICALIZE_HOST_NAME" property will be converted to "true".
144 *
145 * Note: URI options are case-insensitive, so we must iterate through the
146 * HashTable in order to detect options. */
php_phongo_manager_prep_uri_options(zval * options)147 static void php_phongo_manager_prep_uri_options(zval* options) /* {{{ */
148 {
149 HashTable* ht_data;
150
151 if (Z_TYPE_P(options) != IS_ARRAY) {
152 return;
153 }
154
155 ht_data = HASH_OF(options);
156
157 {
158 zend_string* string_key = NULL;
159 zend_ulong num_key = 0;
160 zval* option;
161
162 ZEND_HASH_FOREACH_KEY_VAL_IND(ht_data, num_key, string_key, option)
163 {
164 if (!string_key) {
165 continue;
166 }
167
168 if (!strcasecmp(ZSTR_VAL(string_key), MONGOC_URI_READPREFERENCETAGS)) {
169 ZVAL_DEREF(option);
170 SEPARATE_ZVAL_NOREF(option);
171 php_phongo_read_preference_prep_tagsets(option);
172 continue;
173 }
174
175 if (!strcasecmp(ZSTR_VAL(string_key), MONGOC_URI_AUTHMECHANISMPROPERTIES)) {
176 ZVAL_DEREF(option);
177 SEPARATE_ZVAL_NOREF(option);
178 php_phongo_manager_prep_authmechanismproperties(option);
179 continue;
180 }
181 }
182 ZEND_HASH_FOREACH_END();
183 }
184 } /* }}} */
185
186 /* Selects a server for an execute method. If "for_writes" is true, a primary
187 * will be selected. Otherwise, a read preference will be used to select the
188 * server. If zreadPreference is NULL, the client's read preference will be
189 * used. If zsession is a session object in a sharded transaction, the session
190 * will be checked whether it is pinned to a server. If so, that server will be
191 * selected. Otherwise, server selection
192 *
193 * On success, server_id will be set and the function will return true;
194 * otherwise, false is returned and an exception is thrown. */
php_phongo_manager_select_server(bool for_writes,bool inherit_read_preference,zval * zreadPreference,zval * zsession,mongoc_client_t * client,uint32_t * server_id)195 static bool php_phongo_manager_select_server(bool for_writes, bool inherit_read_preference, zval* zreadPreference, zval* zsession, mongoc_client_t* client, uint32_t* server_id) /* {{{ */
196 {
197 mongoc_server_description_t* selected_server;
198 const mongoc_read_prefs_t* read_preference = NULL;
199 bson_error_t error = { 0 };
200
201 if (zsession) {
202 const mongoc_client_session_t* session = Z_SESSION_OBJ_P(zsession)->client_session;
203
204 /* Attempt to fetch server pinned to session */
205 if (mongoc_client_session_get_server_id(session) > 0) {
206 *server_id = mongoc_client_session_get_server_id(session);
207
208 return true;
209 }
210 }
211
212 if (!for_writes) {
213 if (zreadPreference) {
214 read_preference = phongo_read_preference_from_zval(zreadPreference);
215 } else if (inherit_read_preference) {
216 read_preference = mongoc_client_get_read_prefs(client);
217 }
218 }
219
220 selected_server = mongoc_client_select_server(client, for_writes, read_preference, &error);
221
222 if (selected_server) {
223 *server_id = mongoc_server_description_id(selected_server);
224 mongoc_server_description_destroy(selected_server);
225
226 return true;
227 }
228
229 /* Check for connection related exceptions */
230 if (!EG(exception)) {
231 phongo_throw_exception_from_bson_error_t(&error);
232 }
233
234 return false;
235 } /* }}} */
236
237 /* {{{ proto void MongoDB\Driver\Manager::__construct([string $uri = "mongodb://127.0.0.1/"[, array $options = array()[, array $driverOptions = array()]]])
238 Constructs a new Manager */
PHP_METHOD(Manager,__construct)239 static PHP_METHOD(Manager, __construct)
240 {
241 zend_error_handling error_handling;
242 php_phongo_manager_t* intern;
243 char* uri_string = NULL;
244 size_t uri_string_len = 0;
245 zval* options = NULL;
246 zval* driverOptions = NULL;
247
248 intern = Z_MANAGER_OBJ_P(getThis());
249
250 /* Separate the options and driverOptions zvals, since we may end up
251 * modifying them in php_phongo_manager_prep_uri_options() and
252 * php_phongo_manager_merge_context_options() below, respectively. */
253 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
254 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|s!a/!a/!", &uri_string, &uri_string_len, &options, &driverOptions) == FAILURE) {
255 zend_restore_error_handling(&error_handling);
256 return;
257 }
258 zend_restore_error_handling(&error_handling);
259
260 if (options) {
261 php_phongo_manager_prep_uri_options(options);
262 }
263
264 if (driverOptions && !php_phongo_manager_merge_context_options(driverOptions)) {
265 /* Exception should already have been thrown */
266 return;
267 }
268
269 phongo_manager_init(intern, uri_string ? uri_string : PHONGO_MANAGER_URI_DEFAULT, options, driverOptions);
270
271 if (intern->client) {
272 php_phongo_set_monitoring_callbacks(intern->client);
273 }
274 } /* }}} */
275
276 /* {{{ proto MongoDB\Driver\ClientEncryption MongoDB\Driver\Manager::createClientEncryption(array $options)
277 Return a ClientEncryption instance */
PHP_METHOD(Manager,createClientEncryption)278 static PHP_METHOD(Manager, createClientEncryption)
279 {
280 zend_error_handling error_handling;
281 php_phongo_manager_t* intern;
282 php_phongo_clientencryption_t* clientencryption;
283 zval* options;
284
285 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
286 if (zend_parse_parameters(ZEND_NUM_ARGS(), "a", &options) == FAILURE) {
287 zend_restore_error_handling(&error_handling);
288 return;
289 }
290 zend_restore_error_handling(&error_handling);
291
292 intern = Z_MANAGER_OBJ_P(getThis());
293
294 object_init_ex(return_value, php_phongo_clientencryption_ce);
295 clientencryption = Z_CLIENTENCRYPTION_OBJ_P(return_value);
296
297 phongo_clientencryption_init(clientencryption, intern->client, options);
298 } /* }}} */
299
300 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
301 Execute a Command */
PHP_METHOD(Manager,executeCommand)302 static PHP_METHOD(Manager, executeCommand)
303 {
304 zend_error_handling error_handling;
305 php_phongo_manager_t* intern;
306 char* db;
307 size_t db_len;
308 zval* command;
309 zval* options = NULL;
310 bool free_options = false;
311 zval* zreadPreference = NULL;
312 zval* zsession = NULL;
313 uint32_t server_id = 0;
314
315 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
316 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|z!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
317 zend_restore_error_handling(&error_handling);
318 return;
319 }
320 zend_restore_error_handling(&error_handling);
321
322 intern = Z_MANAGER_OBJ_P(getThis());
323
324 options = php_phongo_prep_legacy_option(options, "readPreference", &free_options);
325
326 if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
327 /* Exception should already have been thrown */
328 goto cleanup;
329 }
330
331 if (!phongo_parse_read_preference(options, &zreadPreference)) {
332 /* Exception should already have been thrown */
333 goto cleanup;
334 }
335
336 if (!php_phongo_manager_select_server(false, false, zreadPreference, zsession, intern->client, &server_id)) {
337 /* Exception should already have been thrown */
338 goto cleanup;
339 }
340
341 /* If the Manager was created in a different process, reset the client so
342 * that cursors created by this process can be differentiated and its
343 * session pool is cleared. */
344 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
345
346 phongo_execute_command(intern->client, PHONGO_COMMAND_RAW, db, command, options, server_id, return_value);
347
348 cleanup:
349 if (free_options) {
350 php_phongo_prep_legacy_option_free(options);
351 }
352 } /* }}} */
353
354 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeReadCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
355 Execute a ReadCommand */
PHP_METHOD(Manager,executeReadCommand)356 static PHP_METHOD(Manager, executeReadCommand)
357 {
358 zend_error_handling error_handling;
359 php_phongo_manager_t* intern;
360 char* db;
361 size_t db_len;
362 zval* command;
363 zval* options = NULL;
364 zval* zreadPreference = NULL;
365 uint32_t server_id = 0;
366 zval* zsession = NULL;
367
368 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
369 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|a!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
370 zend_restore_error_handling(&error_handling);
371 return;
372 }
373 zend_restore_error_handling(&error_handling);
374
375 intern = Z_MANAGER_OBJ_P(getThis());
376
377 if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
378 /* Exception should already have been thrown */
379 return;
380 }
381
382 if (!phongo_parse_read_preference(options, &zreadPreference)) {
383 /* Exception should already have been thrown */
384 return;
385 }
386
387 if (!php_phongo_manager_select_server(false, true, zreadPreference, zsession, intern->client, &server_id)) {
388 /* Exception should already have been thrown */
389 return;
390 }
391
392 /* If the Manager was created in a different process, reset the client so
393 * that cursors created by this process can be differentiated and its
394 * session pool is cleared. */
395 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
396
397 phongo_execute_command(intern->client, PHONGO_COMMAND_READ, db, command, options, server_id, return_value);
398 } /* }}} */
399
400 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeWriteCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
401 Execute a WriteCommand */
PHP_METHOD(Manager,executeWriteCommand)402 static PHP_METHOD(Manager, executeWriteCommand)
403 {
404 zend_error_handling error_handling;
405 php_phongo_manager_t* intern;
406 char* db;
407 size_t db_len;
408 zval* command;
409 zval* options = NULL;
410 uint32_t server_id = 0;
411 zval* zsession = NULL;
412
413 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
414 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|a!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
415 zend_restore_error_handling(&error_handling);
416 return;
417 }
418 zend_restore_error_handling(&error_handling);
419
420 intern = Z_MANAGER_OBJ_P(getThis());
421
422 if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
423 /* Exception should already have been thrown */
424 return;
425 }
426
427 if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) {
428 /* Exception should already have been thrown */
429 return;
430 }
431
432 /* If the Manager was created in a different process, reset the client so
433 * that cursors created by this process can be differentiated and its
434 * session pool is cleared. */
435 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
436
437 phongo_execute_command(intern->client, PHONGO_COMMAND_WRITE, db, command, options, server_id, return_value);
438 } /* }}} */
439
440 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeReadWriteCommand(string $db, MongoDB\Driver\Command $command[, array $options = null])
441 Execute a ReadWriteCommand */
PHP_METHOD(Manager,executeReadWriteCommand)442 static PHP_METHOD(Manager, executeReadWriteCommand)
443 {
444 zend_error_handling error_handling;
445 php_phongo_manager_t* intern;
446 char* db;
447 size_t db_len;
448 zval* command;
449 zval* options = NULL;
450 uint32_t server_id = 0;
451 zval* zsession = NULL;
452
453 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
454 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|a!", &db, &db_len, &command, php_phongo_command_ce, &options) == FAILURE) {
455 zend_restore_error_handling(&error_handling);
456 return;
457 }
458 zend_restore_error_handling(&error_handling);
459
460 intern = Z_MANAGER_OBJ_P(getThis());
461
462 if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
463 /* Exception should already have been thrown */
464 return;
465 }
466
467 if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) {
468 /* Exception should already have been thrown */
469 return;
470 }
471
472 /* If the Manager was created in a different process, reset the client so
473 * that cursors created by this process can be differentiated and its
474 * session pool is cleared. */
475 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
476
477 phongo_execute_command(intern->client, PHONGO_COMMAND_READ_WRITE, db, command, options, server_id, return_value);
478 } /* }}} */
479
480 /* {{{ proto MongoDB\Driver\Cursor MongoDB\Driver\Manager::executeQuery(string $namespace, MongoDB\Driver\Query $query[, array $options = null])
481 Execute a Query */
PHP_METHOD(Manager,executeQuery)482 static PHP_METHOD(Manager, executeQuery)
483 {
484 zend_error_handling error_handling;
485 php_phongo_manager_t* intern;
486 char* namespace;
487 size_t namespace_len;
488 zval* query;
489 zval* options = NULL;
490 bool free_options = false;
491 zval* zreadPreference = NULL;
492 uint32_t server_id = 0;
493 zval* zsession = NULL;
494
495 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
496 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|z!", &namespace, &namespace_len, &query, php_phongo_query_ce, &options) == FAILURE) {
497 zend_restore_error_handling(&error_handling);
498 return;
499 }
500 zend_restore_error_handling(&error_handling);
501
502 intern = Z_MANAGER_OBJ_P(getThis());
503
504 options = php_phongo_prep_legacy_option(options, "readPreference", &free_options);
505
506 if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
507 /* Exception should already have been thrown */
508 goto cleanup;
509 }
510
511 if (!phongo_parse_read_preference(options, &zreadPreference)) {
512 /* Exception should already have been thrown */
513 goto cleanup;
514 }
515
516 if (!php_phongo_manager_select_server(false, true, zreadPreference, zsession, intern->client, &server_id)) {
517 /* Exception should already have been thrown */
518 goto cleanup;
519 }
520
521 /* If the Manager was created in a different process, reset the client so
522 * that cursors created by this process can be differentiated and its
523 * session pool is cleared. */
524 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
525
526 phongo_execute_query(intern->client, namespace, query, options, server_id, return_value);
527
528 cleanup:
529 if (free_options) {
530 php_phongo_prep_legacy_option_free(options);
531 }
532 } /* }}} */
533
534 /* {{{ proto MongoDB\Driver\WriteResult MongoDB\Driver\Manager::executeBulkWrite(string $namespace, MongoDB\Driver\BulkWrite $zbulk[, array $options = null])
535 Executes a BulkWrite (i.e. any number of insert, update, and delete ops) */
PHP_METHOD(Manager,executeBulkWrite)536 static PHP_METHOD(Manager, executeBulkWrite)
537 {
538 zend_error_handling error_handling;
539 php_phongo_manager_t* intern;
540 char* namespace;
541 size_t namespace_len;
542 zval* zbulk;
543 php_phongo_bulkwrite_t* bulk;
544 zval* options = NULL;
545 bool free_options = false;
546 uint32_t server_id = 0;
547 zval* zsession = NULL;
548
549 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
550 if (zend_parse_parameters(ZEND_NUM_ARGS(), "sO|z!", &namespace, &namespace_len, &zbulk, php_phongo_bulkwrite_ce, &options) == FAILURE) {
551 zend_restore_error_handling(&error_handling);
552 return;
553 }
554 zend_restore_error_handling(&error_handling);
555
556 intern = Z_MANAGER_OBJ_P(getThis());
557 bulk = Z_BULKWRITE_OBJ_P(zbulk);
558
559 options = php_phongo_prep_legacy_option(options, "writeConcern", &free_options);
560
561 if (!phongo_parse_session(options, intern->client, NULL, &zsession)) {
562 /* Exception should already have been thrown */
563 return;
564 }
565
566 if (!php_phongo_manager_select_server(true, false, NULL, zsession, intern->client, &server_id)) {
567 /* Exception should already have been thrown */
568 goto cleanup;
569 }
570
571 /* If the Server was created in a different process, reset the client so
572 * that its session pool is cleared. */
573 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
574
575 phongo_execute_bulk_write(intern->client, namespace, bulk, options, server_id, return_value);
576
577 cleanup:
578 if (free_options) {
579 php_phongo_prep_legacy_option_free(options);
580 }
581 } /* }}} */
582
583 /* {{{ proto MongoDB\Driver\ReadConcern MongoDB\Driver\Manager::getReadConcern()
584 Returns the ReadConcern associated with this Manager */
PHP_METHOD(Manager,getReadConcern)585 static PHP_METHOD(Manager, getReadConcern)
586 {
587 zend_error_handling error_handling;
588 php_phongo_manager_t* intern;
589
590 intern = Z_MANAGER_OBJ_P(getThis());
591
592 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
593 if (zend_parse_parameters_none() == FAILURE) {
594 zend_restore_error_handling(&error_handling);
595 return;
596 }
597 zend_restore_error_handling(&error_handling);
598
599 phongo_readconcern_init(return_value, mongoc_client_get_read_concern(intern->client));
600 } /* }}} */
601
602 /* {{{ proto MongoDB\Driver\ReadPreference MongoDB\Driver\Manager::getReadPreference()
603 Returns the ReadPreference associated with this Manager */
PHP_METHOD(Manager,getReadPreference)604 static PHP_METHOD(Manager, getReadPreference)
605 {
606 zend_error_handling error_handling;
607 php_phongo_manager_t* intern;
608
609 intern = Z_MANAGER_OBJ_P(getThis());
610
611 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
612 if (zend_parse_parameters_none() == FAILURE) {
613 zend_restore_error_handling(&error_handling);
614 return;
615 }
616 zend_restore_error_handling(&error_handling);
617
618 phongo_readpreference_init(return_value, mongoc_client_get_read_prefs(intern->client));
619 } /* }}} */
620
621 /* {{{ proto MongoDB\Driver\Server[] MongoDB\Driver\Manager::getServers()
622 Returns the Servers associated with this Manager */
PHP_METHOD(Manager,getServers)623 static PHP_METHOD(Manager, getServers)
624 {
625 zend_error_handling error_handling;
626 php_phongo_manager_t* intern;
627 mongoc_server_description_t** sds;
628 size_t i, n = 0;
629
630 intern = Z_MANAGER_OBJ_P(getThis());
631
632 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
633 if (zend_parse_parameters_none() == FAILURE) {
634 zend_restore_error_handling(&error_handling);
635 return;
636 }
637 zend_restore_error_handling(&error_handling);
638
639 sds = mongoc_client_get_server_descriptions(intern->client, &n);
640 array_init_size(return_value, n);
641
642 for (i = 0; i < n; i++) {
643 zval obj;
644
645 phongo_server_init(&obj, intern->client, mongoc_server_description_id(sds[i]));
646 add_next_index_zval(return_value, &obj);
647 }
648
649 mongoc_server_descriptions_destroy_all(sds, n);
650 } /* }}} */
651
652 /* {{{ proto MongoDB\Driver\WriteConcern MongoDB\Driver\Manager::getWriteConcern()
653 Returns the WriteConcern associated with this Manager */
PHP_METHOD(Manager,getWriteConcern)654 static PHP_METHOD(Manager, getWriteConcern)
655 {
656 zend_error_handling error_handling;
657 php_phongo_manager_t* intern;
658
659 intern = Z_MANAGER_OBJ_P(getThis());
660
661 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
662 if (zend_parse_parameters_none() == FAILURE) {
663 zend_restore_error_handling(&error_handling);
664 return;
665 }
666 zend_restore_error_handling(&error_handling);
667
668 phongo_writeconcern_init(return_value, mongoc_client_get_write_concern(intern->client));
669 } /* }}} */
670
671 /* {{{ proto MongoDB\Driver\Server MongoDB\Driver\Manager::selectServers(MongoDB\Driver\ReadPreference $readPreference)
672 Returns a suitable Server for the given ReadPreference */
PHP_METHOD(Manager,selectServer)673 static PHP_METHOD(Manager, selectServer)
674 {
675 zend_error_handling error_handling;
676 php_phongo_manager_t* intern;
677 zval* zreadPreference = NULL;
678 uint32_t server_id = 0;
679
680 intern = Z_MANAGER_OBJ_P(getThis());
681
682 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
683 if (zend_parse_parameters(ZEND_NUM_ARGS(), "O", &zreadPreference, php_phongo_readpreference_ce) == FAILURE) {
684 zend_restore_error_handling(&error_handling);
685 return;
686 }
687 zend_restore_error_handling(&error_handling);
688
689 if (!php_phongo_manager_select_server(false, true, zreadPreference, NULL, intern->client, &server_id)) {
690 /* Exception should already have been thrown */
691 return;
692 }
693
694 phongo_server_init(return_value, intern->client, server_id);
695 } /* }}} */
696
697 /* {{{ proto MongoDB\Driver\Session MongoDB\Driver\Manager::startSession([array $options = null])
698 Returns a new client session */
PHP_METHOD(Manager,startSession)699 static PHP_METHOD(Manager, startSession)
700 {
701 zend_error_handling error_handling;
702 php_phongo_manager_t* intern;
703 zval* options = NULL;
704 mongoc_session_opt_t* cs_opts = NULL;
705 mongoc_client_session_t* cs;
706 bson_error_t error = { 0 };
707 mongoc_transaction_opt_t* txn_opts = NULL;
708
709 intern = Z_MANAGER_OBJ_P(getThis());
710
711 zend_replace_error_handling(EH_THROW, phongo_exception_from_phongo_domain(PHONGO_ERROR_INVALID_ARGUMENT), &error_handling);
712 if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &options) == FAILURE) {
713 zend_restore_error_handling(&error_handling);
714 return;
715 }
716 zend_restore_error_handling(&error_handling);
717
718 if (options && php_array_existsc(options, "causalConsistency")) {
719 cs_opts = mongoc_session_opts_new();
720 mongoc_session_opts_set_causal_consistency(cs_opts, php_array_fetchc_bool(options, "causalConsistency"));
721 }
722
723 if (options && php_array_existsc(options, "defaultTransactionOptions")) {
724 zval* txn_options = php_array_fetchc(options, "defaultTransactionOptions");
725
726 /* Thrown exception and return if the defaultTransactionOptions is not an array */
727 if (Z_TYPE_P(txn_options) != IS_ARRAY) {
728 phongo_throw_exception(
729 PHONGO_ERROR_INVALID_ARGUMENT,
730 "Expected \"defaultTransactionOptions\" option to be an array, %s given",
731 PHONGO_ZVAL_CLASS_OR_TYPE_NAME_P(txn_options));
732 goto cleanup;
733 }
734
735 /* Parse transaction options */
736 txn_opts = php_mongodb_session_parse_transaction_options(txn_options);
737
738 /* If an exception is thrown while parsing, the txn_opts struct is also
739 * NULL, so no need to free it here */
740 if (EG(exception)) {
741 goto cleanup;
742 }
743
744 /* If the options are non-empty, add them to the client session opts struct */
745 if (txn_opts) {
746 if (!cs_opts) {
747 cs_opts = mongoc_session_opts_new();
748 }
749
750 mongoc_session_opts_set_default_transaction_opts(cs_opts, txn_opts);
751 mongoc_transaction_opts_destroy(txn_opts);
752 }
753 }
754
755 /* If the Manager was created in a different process, reset the client so
756 * that its session pool is cleared. This will ensure that we do not re-use
757 * a server session (i.e. LSID) created by a parent process. */
758 PHONGO_RESET_CLIENT_IF_PID_DIFFERS(intern);
759
760 cs = mongoc_client_start_session(intern->client, cs_opts, &error);
761
762 if (cs) {
763 phongo_session_init(return_value, cs);
764 } else {
765 phongo_throw_exception_from_bson_error_t(&error);
766 }
767
768 cleanup:
769 if (cs_opts) {
770 mongoc_session_opts_destroy(cs_opts);
771 }
772 } /* }}} */
773
774 /* {{{ MongoDB\Driver\Manager function entries */
775 ZEND_BEGIN_ARG_INFO_EX(ai_Manager___construct, 0, 0, 0)
776 ZEND_ARG_INFO(0, uri)
777 ZEND_ARG_ARRAY_INFO(0, options, 0)
778 ZEND_ARG_ARRAY_INFO(0, driverOptions, 0)
779 ZEND_END_ARG_INFO()
780
781 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_createClientEncryption, 0, 0, 1)
782 ZEND_ARG_ARRAY_INFO(0, options, 0)
783 ZEND_END_ARG_INFO()
784
785 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeCommand, 0, 0, 2)
786 ZEND_ARG_INFO(0, db)
787 ZEND_ARG_OBJ_INFO(0, command, MongoDB\\Driver\\Command, 0)
788 ZEND_ARG_INFO(0, options)
789 ZEND_END_ARG_INFO()
790
791 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeRWCommand, 0, 0, 2)
792 ZEND_ARG_INFO(0, db)
793 ZEND_ARG_OBJ_INFO(0, command, MongoDB\\Driver\\Command, 0)
794 ZEND_ARG_ARRAY_INFO(0, options, 0)
795 ZEND_END_ARG_INFO()
796
797 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeQuery, 0, 0, 2)
798 ZEND_ARG_INFO(0, namespace)
799 ZEND_ARG_OBJ_INFO(0, zquery, MongoDB\\Driver\\Query, 0)
800 ZEND_ARG_INFO(0, options)
801 ZEND_END_ARG_INFO()
802
803 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_executeBulkWrite, 0, 0, 2)
804 ZEND_ARG_INFO(0, namespace)
805 ZEND_ARG_OBJ_INFO(0, zbulk, MongoDB\\Driver\\BulkWrite, 0)
806 ZEND_ARG_INFO(0, options)
807 ZEND_END_ARG_INFO()
808
809 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_selectServer, 0, 0, 1)
810 ZEND_ARG_OBJ_INFO(0, readPreference, MongoDB\\Driver\\ReadPreference, 0)
811 ZEND_END_ARG_INFO()
812
813 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_startSession, 0, 0, 0)
814 ZEND_ARG_ARRAY_INFO(0, options, 1)
815 ZEND_END_ARG_INFO()
816
817 ZEND_BEGIN_ARG_INFO_EX(ai_Manager_void, 0, 0, 0)
818 ZEND_END_ARG_INFO()
819
820 static zend_function_entry php_phongo_manager_me[] = {
821 /* clang-format off */
822 PHP_ME(Manager, __construct, ai_Manager___construct, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
823 PHP_ME(Manager, createClientEncryption, ai_Manager_createClientEncryption, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
824 PHP_ME(Manager, executeCommand, ai_Manager_executeCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
825 PHP_ME(Manager, executeReadCommand, ai_Manager_executeRWCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
826 PHP_ME(Manager, executeWriteCommand, ai_Manager_executeRWCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
827 PHP_ME(Manager, executeReadWriteCommand, ai_Manager_executeCommand, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
828 PHP_ME(Manager, executeQuery, ai_Manager_executeQuery, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
829 PHP_ME(Manager, executeBulkWrite, ai_Manager_executeBulkWrite, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
830 PHP_ME(Manager, getReadConcern, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
831 PHP_ME(Manager, getReadPreference, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
832 PHP_ME(Manager, getServers, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
833 PHP_ME(Manager, getWriteConcern, ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
834 PHP_ME(Manager, selectServer, ai_Manager_selectServer, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
835 PHP_ME(Manager, startSession, ai_Manager_startSession, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
836 ZEND_NAMED_ME(__wakeup, PHP_FN(MongoDB_disabled___wakeup), ai_Manager_void, ZEND_ACC_PUBLIC | ZEND_ACC_FINAL)
837 PHP_FE_END
838 /* clang-format on */
839 };
840 /* }}} */
841
842 /* {{{ MongoDB\Driver\Manager object handlers */
843 static zend_object_handlers php_phongo_handler_manager;
844
php_phongo_manager_free_object(zend_object * object)845 static void php_phongo_manager_free_object(zend_object* object) /* {{{ */
846 {
847 php_phongo_manager_t* intern = Z_OBJ_MANAGER(object);
848
849 zend_object_std_dtor(&intern->std);
850
851 if (intern->client) {
852 MONGOC_DEBUG("Not destroying persistent client for Manager");
853 intern->client = NULL;
854 }
855
856 if (intern->client_hash) {
857 efree(intern->client_hash);
858 }
859 } /* }}} */
860
php_phongo_manager_create_object(zend_class_entry * class_type)861 static zend_object* php_phongo_manager_create_object(zend_class_entry* class_type) /* {{{ */
862 {
863 php_phongo_manager_t* intern = NULL;
864
865 intern = PHONGO_ALLOC_OBJECT_T(php_phongo_manager_t, class_type);
866
867 zend_object_std_init(&intern->std, class_type);
868 object_properties_init(&intern->std, class_type);
869
870 PHONGO_SET_CREATED_BY_PID(intern);
871
872 intern->std.handlers = &php_phongo_handler_manager;
873
874 return &intern->std;
875 } /* }}} */
876
php_phongo_manager_get_debug_info(phongo_compat_object_handler_type * object,int * is_temp)877 static HashTable* php_phongo_manager_get_debug_info(phongo_compat_object_handler_type* object, int* is_temp) /* {{{ */
878 {
879 php_phongo_manager_t* intern;
880 mongoc_server_description_t** sds;
881 size_t i, n = 0;
882 zval retval = ZVAL_STATIC_INIT;
883 zval cluster;
884
885 *is_temp = 1;
886 intern = Z_OBJ_MANAGER(PHONGO_COMPAT_GET_OBJ(object));
887
888 array_init_size(&retval, 2);
889
890 ADD_ASSOC_STRING(&retval, "uri", mongoc_uri_get_string(mongoc_client_get_uri(intern->client)));
891
892 sds = mongoc_client_get_server_descriptions(intern->client, &n);
893
894 array_init_size(&cluster, n);
895
896 for (i = 0; i < n; i++) {
897 zval obj;
898
899 if (!php_phongo_server_to_zval(&obj, sds[i])) {
900 /* Exception already thrown */
901 zval_ptr_dtor(&obj);
902 zval_ptr_dtor(&cluster);
903 goto done;
904 }
905
906 add_next_index_zval(&cluster, &obj);
907 }
908
909 ADD_ASSOC_ZVAL_EX(&retval, "cluster", &cluster);
910
911 done:
912 mongoc_server_descriptions_destroy_all(sds, n);
913
914 return Z_ARRVAL(retval);
915 } /* }}} */
916 /* }}} */
917
php_phongo_manager_init_ce(INIT_FUNC_ARGS)918 void php_phongo_manager_init_ce(INIT_FUNC_ARGS) /* {{{ */
919 {
920 zend_class_entry ce;
921
922 INIT_NS_CLASS_ENTRY(ce, "MongoDB\\Driver", "Manager", php_phongo_manager_me);
923 php_phongo_manager_ce = zend_register_internal_class(&ce);
924 php_phongo_manager_ce->create_object = php_phongo_manager_create_object;
925 PHONGO_CE_FINAL(php_phongo_manager_ce);
926 PHONGO_CE_DISABLE_SERIALIZATION(php_phongo_manager_ce);
927
928 memcpy(&php_phongo_handler_manager, phongo_get_std_object_handlers(), sizeof(zend_object_handlers));
929 php_phongo_handler_manager.get_debug_info = php_phongo_manager_get_debug_info;
930 php_phongo_handler_manager.free_obj = php_phongo_manager_free_object;
931 php_phongo_handler_manager.offset = XtOffsetOf(php_phongo_manager_t, std);
932 } /* }}} */
933
934 /*
935 * Local variables:
936 * tab-width: 4
937 * c-basic-offset: 4
938 * End:
939 * vim600: noet sw=4 ts=4 fdm=marker
940 * vim<600: noet sw=4 ts=4
941 */
942