Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHPLIB-1249 Init code generator project for aggregation builder #1174

Merged
merged 31 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
96e03ea
PHPLIB-1249 Init code generator project
GromNaN Sep 25, 2023
42337fd
Use promoted properties for config validation
GromNaN Sep 25, 2023
e269731
Remove one useless config level
GromNaN Sep 25, 2023
725c5ef
Implement FactoryClassGenerator
GromNaN Sep 25, 2023
32d768f
Move to MongoDB\builder namespace
GromNaN Sep 26, 2023
9f29f28
Add Expression interfaces
GromNaN Sep 26, 2023
194f544
Commit generated classes
GromNaN Sep 26, 2023
900c748
Add minimum for variadic arguments
GromNaN Sep 26, 2023
1ad50f8
Add expression types
GromNaN Sep 27, 2023
2b60cf7
Init BuilderCodec with a unit test
GromNaN Sep 27, 2023
02ae084
Cleanup
GromNaN Sep 27, 2023
d8c3776
Merge multiple arg types
GromNaN Sep 27, 2023
d0ef3bb
Add typed FieldPath classes
GromNaN Sep 28, 2023
c1e2cb4
Pedantry
GromNaN Sep 28, 2023
3fbb7da
Simplify expected pipeline in tests by converting assoc array to objects
GromNaN Sep 28, 2023
eed8ea5
Fix CS
GromNaN Sep 28, 2023
c137337
Remove useless ACCEPTED_TYPES constant. Pass types config to the gene…
GromNaN Sep 28, 2023
1084b1a
Fix psalm issues
GromNaN Sep 28, 2023
a3eef39
Static analysis
GromNaN Sep 29, 2023
63aa368
Add FieldName type
GromNaN Oct 2, 2023
078d679
Reractor operators config to use avoid redondancy
GromNaN Oct 2, 2023
97e0329
Use assert instead of if...throw to validate config
GromNaN Oct 2, 2023
e62d243
Add Aggregation Builder example
GromNaN Oct 2, 2023
fdf8f5a
Implement variadic options
GromNaN Oct 2, 2023
55f8cce
Typo namespace
GromNaN Oct 2, 2023
ace7f60
Assert types is a list of strings
GromNaN Oct 2, 2023
cae32c0
Make generator a single-command app
GromNaN Oct 2, 2023
0e80bb6
Add Optional::Undefined for optional values
GromNaN Oct 2, 2023
d4e53d4
Partial implementation of
GromNaN Oct 2, 2023
c789074
Fix type for $limit
GromNaN Oct 2, 2023
5bf4753
Add object and double to type system
GromNaN Oct 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ tests export-ignore
benchmark export-ignore
docs export-ignore
examples export-ignore
generator export-ignore
mongo-orchestration export-ignore
stubs export-ignore
tools export-ignore
Expand All @@ -14,3 +15,15 @@ phpunit.evergreen.xml export-ignore
phpunit.xml.dist export-ignore
psalm.xml.dist export-ignore
psalm-baseline.xml export-ignore

# Keep generated files from displaying in diffs by default
# https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github
/src/Builder/Aggregation.php linguist-generated=true
/src/Builder/Aggregation/*.php linguist-generated=true
/src/Builder/Expression.php linguist-generated=true
/src/Builder/Expression/*.php linguist-generated=true
/src/Builder/Query.php linguist-generated=true
/src/Builder/Query/*.php linguist-generated=true
/src/Builder/Stage.php linguist-generated=true
/src/Builder/Stage/*.php linguist-generated=true
/src/Builder/Stage/Stage.php linguist-generated=false
18 changes: 18 additions & 0 deletions generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Code Generator for MongoDB PHP Library

This subproject is used to generate the code that is committed to the repository.
The `generator` directory is not included in `mongodb/mongodb` package and is not installed by Composer.

## Contributing

Updating the generated code can be done only by modifying the code generator, or its configuration.

To run the generator, you need to have PHP 8.2+ installed and Composer.

1. Move to the `generator` directory: `cd generator`
1. Install dependencies: `composer install`
1. Run the generator: `bin/console generate`
jmikola marked this conversation as resolved.
Show resolved Hide resolved

## Configuration

The `generator/config/*.yaml` files contains the list of operators and stages that are supported by the library.
16 changes: 16 additions & 0 deletions generator/bin/console
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env php
<?php declare(strict_types=1);

use MongoDB\CodeGenerator\Command\GenerateCommand;
use Symfony\Component\Console\Application;

if (!file_exists(__DIR__ . '/../vendor/autoload.php')) {
echo "Run `composer install` before you run the `console` script.\n";
exit(1);
}

require __DIR__ . '/../vendor/autoload.php';

$application = new Application();
$application->add(new GenerateCommand(__DIR__ . '/../../', __DIR__ . '/../config'));
$application->run();
32 changes: 32 additions & 0 deletions generator/composer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "mongodb/code-generator",
"type": "project",
"repositories": [
{
"type": "path",
"url": "../",
"symlink": true
}
],
"replace": {
"symfony/polyfill-php80": "*",
"symfony/polyfill-php81": "*"
},
"require": {
"php": ">=8.2",
"ext-mongodb": "^1.16.0",
"mongodb/mongodb": "@dev",
jmikola marked this conversation as resolved.
Show resolved Hide resolved
"nette/php-generator": "^4",
"symfony/console": "^6.3",
"symfony/yaml": "^6.3"
},
"license": "Apache-2.0",
"autoload": {
"psr-4": {
"MongoDB\\CodeGenerator\\": "src/"
}
},
"config": {
"sort-packages": true
}
}
110 changes: 110 additions & 0 deletions generator/config/expressions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

// Target namespace for the generated files, allows to use ::class notation without use statements

namespace MongoDB\Builder\Expression;

use MongoDB\BSON;
use MongoDB\Model\BSONArray;
use stdClass;

/** @param class-string $resolvesTo */
function typeFieldPath(string $resolvesTo): array
{
return [
'class' => true,
'extends' => FieldPath::class,
'implements' => [$resolvesTo],
'types' => ['string'],
];
}

return [
// Use Interface suffix to avoid confusion with MongoDB\Builder\Expression factory class
ExpressionInterface::class => [
'types' => ['mixed'],
jmikola marked this conversation as resolved.
Show resolved Hide resolved
],
// @todo must not start with $
// Allows ORMs to translate field names
FieldName::class => [
'class' => true,
'types' => ['string'],
],
// @todo if replaced by a string, it must start with $
FieldPath::class => [
'class' => true,
'implements' => [ExpressionInterface::class],
'types' => ['string'],
],
// @todo if replaced by a string, it must start with $$
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked by PHPLIB-1265

Variable::class => [
'class' => true,
'implements' => [ExpressionInterface::class],
'types' => ['string'],
],
Literal::class => [
'class' => true,
'implements' => [ExpressionInterface::class],
'types' => ['mixed'],
],
// @todo check for use-case
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tracked by PHPLIB-1251

ExpressionObject::class => [
'implements' => [ExpressionInterface::class],
'types' => ['array', stdClass::class, BSON\Document::class, BSON\Serializable::class],
],
// @todo check for use-case
Operator::class => [
'implements' => [ExpressionInterface::class],
'types' => ['array', stdClass::class, BSON\Document::class, BSON\Serializable::class],
],
ResolvesToArray::class => [
'implements' => [ExpressionInterface::class],
'types' => ['list', BSONArray::class, BSON\PackedArray::class],
],
ArrayFieldPath::class => typeFieldPath(ResolvesToArray::class),
ResolvesToBool::class => [
'implements' => [ExpressionInterface::class],
'types' => ['bool'],
],
BoolFieldPath::class => typeFieldPath(ResolvesToBool::class),
ResolvesToDate::class => [
'implements' => [ExpressionInterface::class],
'types' => ['DateTimeInterface', 'UTCDateTime'],
],
DateFieldPath::class => typeFieldPath(ResolvesToDate::class),
ResolvesToObject::class => [
'implements' => [ExpressionInterface::class],
'types' => ['array', 'object', BSON\Document::class, BSON\Serializable::class],
],
ObjectFieldPath::class => typeFieldPath(ResolvesToObject::class),
ResolvesToNull::class => [
'implements' => [ExpressionInterface::class],
'types' => ['null'],
],
NullFieldPath::class => typeFieldPath(ResolvesToNull::class),
ResolvesToNumber::class => [
'implements' => [ExpressionInterface::class],
'types' => ['int', 'float', BSON\Int64::class, BSON\Decimal128::class],
],
NumberFieldPath::class => typeFieldPath(ResolvesToNumber::class),
ResolvesToDecimal::class => [
'implements' => [ResolvesToNumber::class],
'types' => ['int', 'float', BSON\Int64::class, BSON\Decimal128::class],
],
DecimalFieldPath::class => typeFieldPath(ResolvesToDecimal::class),
ResolvesToFloat::class => [
'implements' => [ResolvesToNumber::class],
'types' => ['int', 'float', BSON\Int64::class],
],
FloatFieldPath::class => typeFieldPath(ResolvesToFloat::class),
ResolvesToInt::class => [
'implements' => [ResolvesToNumber::class],
'types' => ['int', BSON\Int64::class],
],
IntFieldPath::class => typeFieldPath(ResolvesToInt::class),
ResolvesToString::class => [
'implements' => [ExpressionInterface::class],
'types' => ['string'],
],
StringFieldPath::class => typeFieldPath(ResolvesToString::class),
];
41 changes: 41 additions & 0 deletions generator/config/operators.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace MongoDB\CodeGenerator\Config;

use MongoDB\CodeGenerator\OperatorClassGenerator;
use MongoDB\CodeGenerator\OperatorFactoryGenerator;

return [
// Aggregation Pipeline Stages
[
'configFile' => __DIR__ . '/stages.yaml',
'namespace' => 'MongoDB\\Builder\\Stage',
'classNameSuffix' => 'Stage',
'generators' => [
OperatorClassGenerator::class,
OperatorFactoryGenerator::class,
],
],
jmikola marked this conversation as resolved.
Show resolved Hide resolved

// Aggregation Pipeline Operators
[
'configFile' => __DIR__ . '/pipeline-operators.yaml',
'namespace' => 'MongoDB\\Builder\\Aggregation',
'classNameSuffix' => 'Aggregation',
'generators' => [
OperatorClassGenerator::class,
OperatorFactoryGenerator::class,
],
],

// Query Operators
[
'configFile' => __DIR__ . '/query-operators.yaml',
'namespace' => 'MongoDB\\Builder\\Query',
'classNameSuffix' => 'Query',
'generators' => [
OperatorClassGenerator::class,
OperatorFactoryGenerator::class,
],
],
];
66 changes: 66 additions & 0 deletions generator/config/pipeline-operators.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
- name: and
type: resolvesToBool
args:
- name: expressions
type: expression
isVariadic: true
variadicMin: 1
- name: eq
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not an issue here, but we should consider splitting this file into multiple smaller files in the future.

encode: array
type: resolvesToBool
args:
- name: expression1
type: expression
- name: expression2
type: expression
- name: gt
encode: array
type: resolvesToBool
args:
- name: expression1
type: expression
- name: expression2
type: expression
- name: gte
encode: array
type: resolvesToBool
args:
- name: expression1
type: expression
- name: expression2
type: expression
- name: lt
encode: array
type: resolvesToBool
args:
- name: expression1
type: expression
- name: expression2
type: expression
- name: ne
encode: array
type: resolvesToBool
args:
- name: expression1
type: expression
- name: expression2
type: expression
- name: filter
encode: object
type: resolvesToArray
args:
- name: input
type: resolvesToArray
- name: cond
type: resolvesToBool
- name: as
type: resolvesToString
isOptional: true
- name: limit
type: resolvesToInt
isOptional: true
- name: sum
type: resolvesToInt
args:
- name: expression
type: expression
32 changes: 32 additions & 0 deletions generator/config/query-operators.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
- name: and
type: resolvesToBool
args:
- name: query
type: resolvesToBool
isVariadic: true
GromNaN marked this conversation as resolved.
Show resolved Hide resolved
- name: or
type: resolvesToBool
args:
- name: query
type: expression
isVariadic: true
- name: expr
type: expression
args:
- name: expression
type: expression
- name: gt
type: resolvesToBool
args:
- name: value
type: expression
- name: lt
type: resolvesToBool
args:
- name: value
type: expression
- name: gte
type: resolvesToBool
args:
- name: value
type: expression
32 changes: 32 additions & 0 deletions generator/config/stages.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
- name: match
type: stage
args:
- name: query
type: expression
- name: sort
type: stage
args:
- name: sortSpecification
type: resolvesToObject
- name: limit
type: stage
args:
- name: limit
type: resolvesToInt
- name: group
type: stage
encode: object
args:
# @todo _id is not optional but only nullable
- name: _id
type: expression
isOptional: true
# @todo fields are optional and encoded at the same level as _id
- name: fields
type: resolvesToObject
isOptional: true
- name: project
type: stage
args:
- name: specifications
type: resolvesToObject
Loading