How to overcome PHP's naming limitations to model MongoDB operators

DDD
Release: 2023-10-18 10:58:01
forward
774 people have browsed it

MongoDB provides drivers for various languages ​​including PHP. To simplify the process of creating aggregation pipelines in PHP, we need to model all stages and operators as functions that can be composed.

Aggregation pipeline is a list of "stage" documents. We'll give an example of querying $match and joining using $lookup:

db.orders.aggregate([
    {
        $match: {
            $or: [
                { status: "shipped" },
                { created_at: { $gte: ISODate("2023-01-01T00:00:00Z") } }
            ]
        }
    },
    {
        $lookup: {
            from: "inventory",
            localField: "product_id",
            foreignField: "product_id",
            as: "inventory_docs"
        }
    }
])
Copy after login

Each key with a dollar prefix is ​​an operator for which we want to provide a factory method.

Namespace functions

The most obvious solution is to create a namespace function, for example: MongoDBOperatoreqof$eq operator.

namespace MongoDB\Operator;
function eq(mixed $value): array {
    return ['$eq' => $value];
}
function lookup(string $from, string $localField, string $foreignField, string $as): array {
    return ['$lookup' => [
        'from' => $from,
        'localField' => $localField,
        'foreignField' => $foreignField,
        'as' => $as,
    ]];
}
Copy after login

Using functions with named parameters, pipes will be written in PHP:

pipeline(
    match(
        or(
            query(status: eq('shipped')),
            query(date: gte(new UTCDateTime())),
        ),
    ),
    lookup(from: 'inventory', localField: 'product_id', foreignField: 'product_id', as: 'inventory_docs'),
);
Copy after login

However, some operator names conflict with reserved keywords in PHP. We cannot create functions (global or namespace) with the following names:

and,

or,

match,

unset,

set,

Add suffix to function name

To avoid the problem of name retention, we can add prefix or suffix to function name.

Suffixed with operator type:

function andQuery(...) { /* ... */ }
function matchStage(...) { /* ... */ }
Copy after login

Underlined:

function _and(...) { /* ... */ }
function _match(...) { /* ... */ }
Copy after login

Or use emoticons. Pretty, but impractical:

function ?and(...) { /* ... */ }
function ?match(...) { /* ... */ }
Copy after login

Static class method

As it happens, the list of reserved keywords for method names is shorter. We can create static methods on classes.

final class Stage {
    public static function lookup(...) { /* ... */ }
    public static function match(...) { /* ... */ }
}
final class Query {
    public static function and(...) { /* ... */ }
    public static function eq(...) { /* ... */ }
}
Copy after login

The writing is a bit long, but it’s still readable.

new Pipeline(
    Stage::match(
        Query::or(
            Query::query(status: Query::eq('shipped')),
            Query::query(date: Query::gte(new UTCDateTime())),
        ),
    ),
    Stage::lookup(from: 'inventory', localField: 'product_id', foreignField: 'product_id', as: 'inventory_docs'),
);
Copy after login

To prevent anyone from creating an instance of this class, we can make the constructor private.

final class Operator {
    // ...
    private function __construct() {} // This constructor cannot be called 
}
Copy after login

We can also use enum without shell. Enum accepts static methods and cannot be instantiated.

enum Query {
    public static function and() { /* ... */ }
    public static function eq() { /* ... */ }
}
Copy after login

Both class and enumeration static methods can be called in the same way.

Closures in variables

Since we couldn’t find an ideal solution, we started to get enthusiastic about unlikely solutions.

If we want a short syntax that looks very similar to MongoDB syntax without name restrictions, then we would think of using variables to store closures. Note that this (...) is the new syntax for creating closures in PHP 8.1.

$eq = Operator::eq(...);
$and = Operator::and(...);
Copy after login

$PHP uses a dollar sign for variable prefixes and MongoDB uses the same operator for prefixes.

pipeline(
    $match(
        $or(
            $query(status: $eq('shipped')),
            $query(date: $gte(new UTCDateTime())),
        ),
    ),
    $lookup(from: 'inventory', localField: 'product_id', foreignField: 'product_id', as: 'inventory_docs'),
);
Copy after login

The library can provide these closures as arrays.

enum Query {
    public static function and(array ...$queries) { /* ... */ }
    public static function eq(mixed $value) { /* ... */ }
    public static function query(mixed ...$query) { /* ... */ }
    /** @return array{and:callable,eq:callable,query:callable} */
    public static function functions(): array {
        return [
            'and' => self::and(...),
            'eq' => self::eq(...),
            'query' => self::query(...),
        ];
    }
}
Copy after login

The syntax for getting all variables is a bit verbose, but still readable.

['and' => $and, 'eq' => $eq, 'query' => $query] = Query::functions();
Copy after login

extract We can import all variables into the current scope using a magical feature in Laravel that is often used but is hated by PHPStorm and static analysis tools.

extract(Query::functions());
var_dump($and(
    $query(foo: $eq(5)),
    $query(bar: $eq(10))
));
// INFO: MixedFunctionCall - Cannot call function on mixed
Copy after login

Conclusion

As you can see, function naming in PHP is not that simple when using reserved keywords.

The above is the detailed content of How to overcome PHP's naming limitations to model MongoDB operators. For more information, please follow other related articles on the PHP Chinese website!

Related labels:
source:Jérôme TAMARELLE
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template
About us Disclaimer Sitemap
php.cn:Public welfare online PHP training,Help PHP learners grow quickly!