functions.php 8.71 KB
Newer Older
1
<?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * Copyright 2015-2017 MongoDB, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
17 18 19

namespace MongoDB;

20
use MongoDB\BSON\Serializable;
21
use MongoDB\Driver\Server;
22
use MongoDB\Driver\Session;
23
use MongoDB\Exception\InvalidArgumentException;
24
use ReflectionClass;
25 26 27 28 29 30 31 32 33 34 35 36
use ReflectionException;
use function end;
use function get_object_vars;
use function in_array;
use function is_array;
use function is_object;
use function is_string;
use function key;
use function MongoDB\BSON\fromPHP;
use function MongoDB\BSON\toPHP;
use function reset;
use function substr;
37

38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/**
 * Applies a type map to a document.
 *
 * This function is used by operations where it is not possible to apply a type
 * map to the cursor directly because the root document is a command response
 * (e.g. findAndModify).
 *
 * @internal
 * @param array|object $document Document to which the type map will be applied
 * @param array        $typeMap  Type map for BSON deserialization.
 * @return array|object
 * @throws InvalidArgumentException
 */
function apply_type_map_to_document($document, array $typeMap)
{
53
    if (! is_array($document) && ! is_object($document)) {
54 55 56
        throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
    }

57
    return toPHP(fromPHP($document), $typeMap);
58 59
}

60 61 62 63 64 65 66
/**
 * Generate an index name from a key specification.
 *
 * @internal
 * @param array|object $document Document containing fields mapped to values,
 *                               which denote order or an index type
 * @return string
67
 * @throws InvalidArgumentException
68 69 70
 */
function generate_index_name($document)
{
71 72 73 74
    if ($document instanceof Serializable) {
        $document = $document->bsonSerialize();
    }

75 76 77 78
    if (is_object($document)) {
        $document = get_object_vars($document);
    }

79
    if (! is_array($document)) {
80
        throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
81 82 83 84 85 86 87 88 89 90 91
    }

    $name = '';

    foreach ($document as $field => $type) {
        $name .= ($name != '' ? '_' : '') . $field . '_' . $type;
    }

    return $name;
}

92 93 94 95 96 97 98 99
/**
 * Return whether the first key in the document starts with a "$" character.
 *
 * This is used for differentiating update and replacement documents.
 *
 * @internal
 * @param array|object $document Update or replacement document
 * @return boolean
100
 * @throws InvalidArgumentException
101 102 103
 */
function is_first_key_operator($document)
{
104 105 106 107
    if ($document instanceof Serializable) {
        $document = $document->bsonSerialize();
    }

108 109 110 111
    if (is_object($document)) {
        $document = get_object_vars($document);
    }

112
    if (! is_array($document)) {
113
        throw InvalidArgumentException::invalidType('$document', $document, 'array or object');
114 115
    }

116
    reset($document);
117 118
    $firstKey = (string) key($document);

119
    return isset($firstKey[0]) && $firstKey[0] === '$';
120 121
}

122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
/**
 * Returns whether an update specification is a valid aggregation pipeline.
 *
 * @internal
 * @param mixed $pipeline
 * @return boolean
 */
function is_pipeline($pipeline)
{
    if (! is_array($pipeline)) {
        return false;
    }

    if ($pipeline === []) {
        return false;
    }

    $expectedKey = 0;

    foreach ($pipeline as $key => $stage) {
        if (! is_array($stage) && ! is_object($stage)) {
            return false;
        }

        if ($expectedKey !== $key) {
            return false;
        }

        $expectedKey++;
        $stage = (array) $stage;
        reset($stage);
        $key = key($stage);

        if (! isset($key[0]) || $key[0] !== '$') {
            return false;
        }
    }

    return true;
}

163 164 165 166 167 168 169 170 171
/**
 * Returns whether we are currently in a transaction.
 *
 * @internal
 * @param array $options Command options
 * @return boolean
 */
function is_in_transaction(array $options)
{
172
    if (isset($options['session']) && $options['session'] instanceof Session && $options['session']->isInTransaction()) {
173 174 175 176 177 178
        return true;
    }

    return false;
}

179
/**
180
 * Return whether the aggregation pipeline ends with an $out or $merge operator.
181
 *
Jeremy Mikola's avatar
Jeremy Mikola committed
182
 * This is used for determining whether the aggregation pipeline must be
183 184 185 186 187 188
 * executed against a primary server.
 *
 * @internal
 * @param array $pipeline List of pipeline operations
 * @return boolean
 */
189
function is_last_pipeline_operator_write(array $pipeline)
190 191 192 193 194 195 196 197 198
{
    $lastOp = end($pipeline);

    if ($lastOp === false) {
        return false;
    }

    $lastOp = (array) $lastOp;

199
    return in_array(key($lastOp), ['$out', '$merge'], true);
200 201
}

202 203 204 205 206 207 208 209 210 211 212 213 214
/**
 * Return whether the "out" option for a mapReduce operation is "inline".
 *
 * This is used to determine if a mapReduce command requires a primary.
 *
 * @internal
 * @see https://docs.mongodb.com/manual/reference/command/mapReduce/#output-inline
 * @param string|array|object $out Output specification
 * @return boolean
 * @throws InvalidArgumentException
 */
function is_mapreduce_output_inline($out)
{
215
    if (! is_array($out) && ! is_object($out)) {
216 217 218 219 220 221 222 223 224 225 226
        return false;
    }

    if ($out instanceof Serializable) {
        $out = $out->bsonSerialize();
    }

    if (is_object($out)) {
        $out = get_object_vars($out);
    }

227
    if (! is_array($out)) {
228 229 230 231 232 233 234 235
        throw InvalidArgumentException::invalidType('$out', $out, 'array or object');
    }

    reset($out);

    return key($out) === 'inline';
}

236 237 238 239 240 241 242 243 244 245 246 247 248 249
/**
 * Return whether the server supports a particular feature.
 *
 * @internal
 * @param Server  $server  Server to check
 * @param integer $feature Feature constant (i.e. wire protocol version)
 * @return boolean
 */
function server_supports_feature(Server $server, $feature)
{
    $info = $server->getInfo();
    $maxWireVersion = isset($info['maxWireVersion']) ? (integer) $info['maxWireVersion'] : 0;
    $minWireVersion = isset($info['minWireVersion']) ? (integer) $info['minWireVersion'] : 0;

250
    return $minWireVersion <= $feature && $maxWireVersion >= $feature;
251
}
252

253 254 255
function is_string_array($input)
{
    if (! is_array($input)) {
256 257
        return false;
    }
258 259
    foreach ($input as $item) {
        if (! is_string($item)) {
260 261 262
            return false;
        }
    }
263

264 265 266
    return true;
}

267 268 269 270 271 272 273 274 275
/**
 * Performs a deep copy of a value.
 *
 * This function will clone objects and recursively copy values within arrays.
 *
 * @internal
 * @see https://bugs.php.net/bug.php?id=49664
 * @param mixed $element Value to be copied
 * @return mixed
276
 * @throws ReflectionException
277
 */
278 279
function recursive_copy($element)
{
280 281 282 283
    if (is_array($element)) {
        foreach ($element as $key => $value) {
            $element[$key] = recursive_copy($value);
        }
284

285 286 287
        return $element;
    }

288
    if (! is_object($element)) {
289 290 291
        return $element;
    }

292
    if (! (new ReflectionClass($element))->isCloneable()) {
293 294 295
        return $element;
    }

296 297
    return clone $element;
}
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340

/**
 * Creates a type map to apply to a field type
 *
 * This is used in the Aggregate, Distinct, and FindAndModify operations to
 * apply the root-level type map to the document that will be returned. It also
 * replaces the root type with object for consistency within these operations
 *
 * An existing type map for the given field path will not be overwritten
 *
 * @internal
 * @param array  $typeMap   The existing typeMap
 * @param string $fieldPath The field path to apply the root type to
 * @return array
 */
function create_field_path_type_map(array $typeMap, $fieldPath)
{
    // If some field paths already exist, we prefix them with the field path we are assuming as the new root
    if (isset($typeMap['fieldPaths']) && is_array($typeMap['fieldPaths'])) {
        $fieldPaths = $typeMap['fieldPaths'];

        $typeMap['fieldPaths'] = [];
        foreach ($fieldPaths as $existingFieldPath => $type) {
            $typeMap['fieldPaths'][$fieldPath . '.' . $existingFieldPath] = $type;
        }
    }

    // If a root typemap was set, apply this to the field object
    if (isset($typeMap['root'])) {
        $typeMap['fieldPaths'][$fieldPath] = $typeMap['root'];
    }

    /* Special case if we want to convert an array, in which case we need to
     * ensure that the field containing the array is exposed as an array,
     * instead of the type given in the type map's array key. */
    if (substr($fieldPath, -2, 2) === '.$') {
        $typeMap['fieldPaths'][substr($fieldPath, 0, -2)] = 'array';
    }

    $typeMap['root'] = 'object';

    return $typeMap;
}