1
0
Fork 0
mirror of synced 2024-06-20 19:50:30 +12:00

Add resolvers for get, list, create, update, delete for user collections

This commit is contained in:
Jake Barnby 2022-04-07 11:23:20 +12:00
parent 69e7c2fed9
commit 48ba76f365
No known key found for this signature in database
GPG key ID: A4674EBC0E404657
9 changed files with 279 additions and 116 deletions

View file

@ -1,6 +1,5 @@
<?php <?php
use Appwrite\GraphQL\GraphQLPromiseAdapter;
use Appwrite\Utopia\Response; use Appwrite\Utopia\Response;
use GraphQL\Error\DebugFlag; use GraphQL\Error\DebugFlag;
use GraphQL\Executor\ExecutionResult; use GraphQL\Executor\ExecutionResult;
@ -29,9 +28,15 @@ App::post('/v1/graphql')
$query = $request->getPayload('query', ''); $query = $request->getPayload('query', '');
$variables = $request->getPayload('variables'); $variables = $request->getPayload('variables');
$response->setContentType(Response::CONTENT_TYPE_NULL); $response->setContentType(Response::CONTENT_TYPE_NULL);
$register->set('__app', function () use ($utopia) {
return $utopia;
});
$register->set('__response', function () use ($response) {
return $response;
});
$isDevelopment = App::isDevelopment(); $isDevelopment = App::isDevelopment();
$debugFlags = $isDevelopment $debugFlags = $isDevelopment

View file

@ -789,6 +789,8 @@ App::setResource('dbForProject', function($db, $cache, $project) {
$database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite')); $database->setDefaultDatabase(App::getEnv('_APP_DB_SCHEMA', 'appwrite'));
$database->setNamespace("_{$project->getId()}"); $database->setNamespace("_{$project->getId()}");
Console::info("Getting dbForProject with ID: {$project->getId()}");
return $database; return $database;
}, ['db', 'cache', 'project']); }, ['db', 'cache', 'project']);
@ -858,16 +860,14 @@ App::setResource('geodb', function($register) {
App::setResource('schema', function($utopia, $response, $request, $register, $dbForProject) { App::setResource('schema', function($utopia, $response, $request, $register, $dbForProject) {
try { try {
// Try to get the schema from the register. Console::log('Getting GraphQL schema from register...');
// If there is no base schema catch the exception and generate it.
// If the base schema exists, extend it with the current project schema.
Console::log('Getting Schema from register...');
$schema = $register->get('_schema'); $schema = $register->get('_schema');
$schema = Builder::appendSchema($schema, $dbForProject); $schema = Builder::appendSchema($schema, $dbForProject);
} catch (Exception $e) { } catch (\Exception $e) {
Console::error('Schema not present. Generating Schema...'); Console::error('Base GraphQL schema not present. Generating...');
$schema = Builder::buildSchema($utopia, $response, $register, $dbForProject); $schema = Builder::buildSchema($utopia, $response, $register, $dbForProject);
$register->set('_schema', function () use ($schema){ Console::error('Built GraphQL schema: ' . \json_encode($schema));
$register->set('_schema', function () use ($schema) {
return $schema; return $schema;
}); });
} }

View file

@ -29,7 +29,7 @@ use Utopia\WebSocket\Adapter;
require_once __DIR__ . '/init.php'; require_once __DIR__ . '/init.php';
Runtime::enableCoroutine(SWOOLE_HOOK_ALL); Runtime::enableCoroutine(true,SWOOLE_HOOK_ALL);
$realtime = new Realtime(); $realtime = new Realtime();

24
composer.lock generated
View file

@ -1583,16 +1583,16 @@
}, },
{ {
"name": "symfony/deprecation-contracts", "name": "symfony/deprecation-contracts",
"version": "v3.0.0", "version": "v3.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git", "url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced" "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/26954b3d62a6c5fd0ea8a2a00c0353a14978d05c",
"reference": "c726b64c1ccfe2896cb7df2e1331c357ad1c8ced", "reference": "26954b3d62a6c5fd0ea8a2a00c0353a14978d05c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1630,7 +1630,7 @@
"description": "A generic function and convention to trigger deprecation notices", "description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.0" "source": "https://github.com/symfony/deprecation-contracts/tree/v3.0.1"
}, },
"funding": [ "funding": [
{ {
@ -1646,7 +1646,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-01T23:48:49+00:00" "time": "2022-01-02T09:55:41+00:00"
}, },
{ {
"name": "symfony/polyfill-ctype", "name": "symfony/polyfill-ctype",
@ -6127,16 +6127,16 @@
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
"version": "v3.0.0", "version": "v3.0.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/service-contracts.git", "url": "https://github.com/symfony/service-contracts.git",
"reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603" "reference": "e517458f278c2131ca9f262f8fbaf01410f2c65c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/service-contracts/zipball/36715ebf9fb9db73db0cb24263c79077c6fe8603", "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e517458f278c2131ca9f262f8fbaf01410f2c65c",
"reference": "36715ebf9fb9db73db0cb24263c79077c6fe8603", "reference": "e517458f278c2131ca9f262f8fbaf01410f2c65c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -6189,7 +6189,7 @@
"standards" "standards"
], ],
"support": { "support": {
"source": "https://github.com/symfony/service-contracts/tree/v3.0.0" "source": "https://github.com/symfony/service-contracts/tree/v3.0.1"
}, },
"funding": [ "funding": [
{ {
@ -6205,7 +6205,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2021-11-04T17:53:12+00:00" "time": "2022-03-13T20:10:05+00:00"
}, },
{ {
"name": "symfony/string", "name": "symfony/string",

View file

@ -727,13 +727,13 @@ services:
# - '3001:80' # - '3001:80'
graphql-explorer: graphql-explorer:
container_name: graphql-explorer container_name: appwrite-graphql-explorer
image: appwrite/altair:0.1.0 image: appwrite/altair:0.1.0
restart: unless-stopped restart: unless-stopped
networks: networks:
- appwrite - appwrite
ports: ports:
- 9509:3000 - "9509:3000"
environment: environment:
- SERVER_URL=http://localhost/v1/graphql - SERVER_URL=http://localhost/v1/graphql

View file

@ -11,6 +11,10 @@ use GraphQL\Type\Definition\ObjectType;
use GraphQL\Type\Definition\Type; use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema; use GraphQL\Type\Schema;
use Utopia\CLI\Console; use Utopia\CLI\Console;
use Utopia\Database\Database;
use Utopia\Database\Document;
use Utopia\Database\Validator\Authorization;
use Utopia\Registry\Registry;
class Builder class Builder
{ {
@ -171,7 +175,7 @@ class Builder
Console::log("[INFO] Appending GraphQL Database Schema..."); Console::log("[INFO] Appending GraphQL Database Schema...");
$start = microtime(true); $start = microtime(true);
$db = self::buildDatabaseSchema($dbForProject); $db = self::buildCollectionsSchema($dbForProject);
$queryFields = $schema->getQueryType()?->getFields() ?? []; $queryFields = $schema->getQueryType()?->getFields() ?? [];
$mutationFields = $schema->getMutationType()?->getFields() ?? []; $mutationFields = $schema->getMutationType()?->getFields() ?? [];
@ -201,10 +205,13 @@ class Builder
return $schema; return $schema;
} }
/**
* @throws \Exception
*/
public static function buildSchema($utopia, $response, $register, $dbForProject): Schema public static function buildSchema($utopia, $response, $register, $dbForProject): Schema
{ {
$db = self::buildDatabaseSchema($dbForProject); $db = self::buildCollectionsSchema($dbForProject, $register);
$api = self::buildAPISchema($utopia, $response, $register, $dbForProject); $api = self::buildAPISchema($utopia, $response, $register);
$queryFields = \array_merge($api['query'], $db['query']); $queryFields = \array_merge($api['query'], $db['query']);
$mutationFields = \array_merge($api['mutation'], $db['mutation']); $mutationFields = \array_merge($api['mutation'], $db['mutation']);
@ -215,12 +222,12 @@ class Builder
return new Schema([ return new Schema([
'query' => new ObjectType([ 'query' => new ObjectType([
'name' => 'Query', 'name' => 'Query',
'description' => 'The root of all your queries', 'description' => 'The root of all queries',
'fields' => $queryFields 'fields' => $queryFields
]), ]),
'mutation' => new ObjectType([ 'mutation' => new ObjectType([
'name' => 'Mutation', 'name' => 'Mutation',
'description' => 'The root of all your mutations', 'description' => 'The root of all mutations',
'fields' => $mutationFields 'fields' => $mutationFields
]) ])
]); ]);
@ -230,77 +237,120 @@ class Builder
* This function goes through all the project attributes and builds a * This function goes through all the project attributes and builds a
* GraphQL schema for all the collections they make up. * GraphQL schema for all the collections they make up.
* *
* @param $dbForProject * @param Database $dbForProject
* @return array * @return array
* @throws \Exception
*/ */
public static function buildDatabaseSchema($dbForProject): array public static function buildCollectionsSchema(Database $dbForProject, Registry &$register): array
{ {
Console::log("[INFO] Building GraphQL Database Schema..."); Console::log("[INFO] Building GraphQL Database Schema...");
$start = microtime(true); $start = microtime(true);
$attrs = $dbForProject->getCollection('attributes'); $collections = [];
$queryFields = []; $queryFields = [];
$mutationFields = []; $mutationFields = [];
$collections = []; $offset = 0;
foreach ($attrs as $attr) { Authorization::skip(function () use ($mutationFields, $queryFields, $collections, $register, $offset, $dbForProject) {
$collectionId = $attr->getAttribute('collectionId'); while (!empty($attrs = $dbForProject->find(
'attributes',
limit: $dbForProject->getAttributeLimit(),
offset: $offset
))) {
go(function ($attrs, $dbForProject, $register, $collections, $queryFields, $mutationFields) {
foreach ($attrs as $attr) {
go(function ($attr, &$collections) {
/** @var Document $attr */
if (isset(self::$typeMapping[$collectionId])) { $collectionId = $attr->getAttribute('collectionId');
continue;
}
$key = $attr->getAttribute('key'); if (isset(self::$typeMapping[$collectionId])) {
$type = $attr->getAttribute('type'); return;
$keyWithoutSpecialChars = str_replace('$', '_', $key); }
if ($attr->getAttribute('status') !== 'available') {
return;
}
$collections[$collectionId][$keyWithoutSpecialChars] = [ $key = $attr->getAttribute('key');
'type' => $type, $type = $attr->getAttribute('type');
'resolve' => function ($object, $args, $context, $info) use ($key) {
return $object->getAttribute($key);
}
];
}
$args = []; $escapedKey = str_replace('$', '_', $key);
foreach ($collections as $id => $fields) { $collections[$collectionId][$escapedKey] = [
$objectType = new ObjectType([ 'type' => $type,
'name' => $id, 'resolve' => function ($object, $args, $context, $info) use ($key) {
'fields' => $fields return $object->getAttribute($key);
]); }
];
self::$typeMapping[$id] = $objectType; }, $attr, $collections);
foreach ($fields as $field => $fieldInfo) {
$args[$field] = [
'type' => $fieldInfo['type']
];
}
$resolve = function ($type, $args, $context, $info) use (&$register, $dbForProject) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($type, $args, $dbForProject) {
try {
$resolve($dbForProject->getCollection($type));
} catch (\Throwable $e) {
$reject($e);
} }
});
};
$field = [ foreach ($collections as $collectionId => $attributes) {
'type' => $type, go(function ($collectionId, $attributes, $dbForProject, $register, &$queryFields, &$mutationFields) {
'args' => $args, if (isset(self::$typeMapping[$collectionId])) {
'resolve' => $resolve return;
]; }
$queryFields[$id] = $field; $objectType = new ObjectType([
$mutationFields[$id] = $field; 'name' => \ucfirst($collectionId),
} 'fields' => $attributes
]);
self::$typeMapping[$collectionId] = $objectType;
$mutateArgs = [];
foreach ($attributes as $name => $attribute) {
$mutateArgs[$name] = [
'type' => $attribute['type']
];
}
$idArgs = [
'id' => [
'type' => Type::string()
]
];
$listArgs = [
'limit' => [
'type' => Type::int()
],
'offset' => [
'type' => Type::int()
],
'cursor' => [
'type' => Type::string()
],
'orderAttributes' => [
'type' => Type::listOf(Type::string())
],
'orderType' => [
'types' => Type::listOf(Type::string())
]
];
self::createCollectionGetQuery($collectionId, $register, $dbForProject, $idArgs, $queryFields);
self::createCollectionListQuery($collectionId, $register, $dbForProject, $listArgs, $queryFields);
self::createCollectionCreateMutation($collectionId, $register, $dbForProject, $mutateArgs, $mutationFields);
self::createCollectionUpdateMutation($collectionId, $register, $dbForProject, $mutateArgs, $mutationFields);
self::createCollectionDeleteMutation($collectionId, $register, $dbForProject, $idArgs, $mutationFields);
}, $collectionId, $attributes, $dbForProject, $register, $queryFields, $mutationFields);
}
}, $attrs, $dbForProject, $register, $collections, $queryFields, $mutationFields);
$offset += $dbForProject->getAttributeLimit();
}
});
$time_elapsed_secs = microtime(true) - $start; $time_elapsed_secs = microtime(true) - $start;
Console::log("[INFO] Time Taken To Build Database Schema : ${time_elapsed_secs}s"); Console::log("[INFO] Time Taken To Build Database Schema : ${time_elapsed_secs}s");
Console::info('[INFO] Schema : ' . json_encode([
'query' => $queryFields,
'mutation' => $mutationFields
]));
return [ return [
'query' => $queryFields, 'query' => $queryFields,
@ -308,6 +358,104 @@ class Builder
]; ];
} }
private static function createCollectionGetQuery($collectionId, $register, $dbForProject, $args, &$queryFields)
{
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try {
$resolve($dbForProject->getDocument($collectionId, $args['id']));
} catch (\Throwable $e) {
$reject($e);
}
});
};
$get = [
'type' => \ucfirst($collectionId),
'args' => $args,
'resolve' => $resolve
];
$queryFields['get' . \ucfirst($collectionId)] = $get;
}
private static function createCollectionListQuery($collectionId, $register, $dbForProject, $args, &$queryFields)
{
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try {
$resolve($dbForProject->getCollection($collectionId));
} catch (\Throwable $e) {
$reject($e);
}
});
};
$list = [
'type' => \ucfirst($collectionId),
'args' => $args,
'resolve' => $resolve
];
$queryFields['list' . \ucfirst($collectionId)] = $list;
}
private static function createCollectionCreateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields)
{
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try {
$resolve($dbForProject->createDocument($collectionId, new Document($args)));
} catch (\Throwable $e) {
$reject($e);
}
});
};
$create = [
'type' => \ucfirst($collectionId),
'args' => $args,
'resolve' => $resolve
];
$mutationFields['create' . \ucfirst($collectionId)] = $create;
}
private static function createCollectionUpdateMutation($collectionId, $register, $dbForProject, $args, &$mutationFields)
{
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try {
$resolve($dbForProject->updateDocument($collectionId, $args['id'], new Document($args)));
} catch (\Throwable $e) {
$reject($e);
}
});
};
$update = [
'type' => \ucfirst($collectionId),
'args' => $args,
'resolve' => $resolve
];
$mutationFields['update' . \ucfirst($collectionId)] = $update;
}
private static function createCollectionDeleteMutation($collectionId, $register, $dbForProject, $args, &$mutationFields)
{
$resolve = function ($type, $args, $context, $info) use ($collectionId, &$register, $dbForProject) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use ($collectionId, $type, $args, $dbForProject) {
try {
$resolve($dbForProject->deleteDocument($collectionId, $args['id']));
} catch (\Throwable $e) {
$reject($e);
}
});
};
$delete = [
'type' => \ucfirst($collectionId),
'args' => $args,
'resolve' => $resolve
];
$mutationFields['delete' . \ucfirst($collectionId)] = $delete;
}
/** /**
* This function goes through all the REST endpoints in the API and builds a * This function goes through all the REST endpoints in the API and builds a
* GraphQL schema for all those routes whose response model is neither empty nor NONE * GraphQL schema for all those routes whose response model is neither empty nor NONE
@ -315,10 +463,9 @@ class Builder
* @param $utopia * @param $utopia
* @param $response * @param $response
* @param $register * @param $register
* @param $dbForProject
* @return array * @return array
*/ */
public static function buildAPISchema($utopia, $response, $register, $dbForProject): array public static function buildAPISchema($utopia, $response, $register): array
{ {
Console::log("[INFO] Building GraphQL API Schema..."); Console::log("[INFO] Building GraphQL API Schema...");
$start = microtime(true); $start = microtime(true);
@ -329,12 +476,17 @@ class Builder
foreach ($utopia->getRoutes() as $method => $routes) { foreach ($utopia->getRoutes() as $method => $routes) {
foreach ($routes as $route) { foreach ($routes as $route) {
$namespace = $route->getLabel('sdk.namespace', ''); $namespace = $route->getLabel('sdk.namespace', '');
$methodName = $namespace . '_' . $route->getLabel('sdk.method', ''); $methodName = $namespace . \ucfirst($route->getLabel('sdk.method', ''));
$responseModelName = $route->getLabel('sdk.response.model', ""); $responseModelName = $route->getLabel('sdk.response.model', "none");
if ($responseModelName !== "") { Console::info("Namespace: $namespace");
Console::info("Method: $methodName");
Console::info("Response Model: $responseModelName");
Console::info("Raw routes: " . \json_encode($routes));
Console::info("Raw route: " . \json_encode($route));
if ($responseModelName !== "none") {
$responseModel = $response->getModel($responseModelName); $responseModel = $response->getModel($responseModelName);
/* Create a GraphQL type for the current response model */ /* Create a GraphQL type for the current response model */
@ -351,8 +503,8 @@ class Builder
]; ];
} }
/* Define a resolve function that defines how to fetch data for this type */ /* Define a resolve function that defines how to fetch data for this type */
$resolve = function ($type, $args, $context, $info) use (&$register, $route, $dbForProject) { $resolve = function ($type, $args, $context, $info) use (&$register, $route) {
return SwoolePromise::create(function (callable $resolve, callable $reject) use (&$register, $route, $dbForProject, $args) { return SwoolePromise::create(function (callable $resolve, callable $reject) use (&$register, $route, $args) {
$utopia = $register->get('__app'); $utopia = $register->get('__app');
$utopia->setRoute($route)->execute($route, $args); $utopia->setRoute($route)->execute($route, $args);
@ -403,12 +555,12 @@ class Builder
* @param string $version * @param string $version
* @return callable * @return callable
*/ */
public public static function getErrorFormatter(bool $isDevelopment, string $version): callable
static function getErrorFormatter(bool $isDevelopment, string $version): callable
{ {
$errorFormatter = function (Error $error) use ($isDevelopment, $version) { return function (Error $error) use ($isDevelopment, $version) {
$formattedError = FormattedError::createFromException($error); $formattedError = FormattedError::createFromException($error);
/** Previous error represents the actual error thrown by Appwrite server */
// Previous error represents the actual error thrown by Appwrite server
$previousError = $error->getPrevious() ?? $error; $previousError = $error->getPrevious() ?? $error;
$formattedError['code'] = $previousError->getCode(); $formattedError['code'] = $previousError->getCode();
$formattedError['version'] = $version; $formattedError['version'] = $version;
@ -418,7 +570,5 @@ class Builder
} }
return $formattedError; return $formattedError;
}; };
return $errorFormatter;
} }
} }

View file

@ -2,8 +2,8 @@
namespace Appwrite\GraphQL; namespace Appwrite\GraphQL;
use Swoole\Coroutine;
use Swoole\Coroutine\Channel; use Swoole\Coroutine\Channel;
use function Co\go;
/** /**
* Class SwoolePromise * Class SwoolePromise
@ -40,7 +40,8 @@ class SwoolePromise
$this->setState(self::STATE_REJECTED); $this->setState(self::STATE_REJECTED);
} }
}; };
Coroutine::create(function (callable $executor, callable $resolve, callable $reject) {
go(function (callable $executor, callable $resolve, callable $reject) {
try { try {
$executor($resolve, $reject); $executor($resolve, $reject);
} catch (\Throwable $exception) { } catch (\Throwable $exception) {

View file

@ -6,17 +6,19 @@ use GraphQL\Error\InvariantViolation;
use GraphQL\Executor\Promise\Promise; use GraphQL\Executor\Promise\Promise;
use GraphQL\Executor\Promise\PromiseAdapter; use GraphQL\Executor\Promise\PromiseAdapter;
use GraphQL\Utils\Utils; use GraphQL\Utils\Utils;
use function Co\go;
use function Co\run;
class GraphQLPromiseAdapter implements PromiseAdapter class SwoolePromiseAdapter implements PromiseAdapter
{ {
public function isThenable($value): bool public function isThenable($value): bool
{ {
return $value instanceof SwoolePromise; return $value instanceof Promise;
} }
public function convertThenable($thenable): Promise public function convertThenable($thenable): Promise
{ {
if (!$thenable instanceof SwoolePromise) { if (!$thenable instanceof Promise) {
throw new InvariantViolation('Expected instance of SwoolePromise, got ' . Utils::printSafe($thenable)); throw new InvariantViolation('Expected instance of SwoolePromise, got ' . Utils::printSafe($thenable));
} }
return new Promise($thenable, $this); return new Promise($thenable, $this);
@ -66,25 +68,30 @@ class GraphQLPromiseAdapter implements PromiseAdapter
$count = 0; $count = 0;
$result = []; $result = [];
foreach ($promisesOrValues as $index => $promiseOrValue) { run(function ($promisesOrValues, $all, $total, &$count, $result) {
if ($promiseOrValue instanceof Promise) { foreach ($promisesOrValues as $index => $promiseOrValue) {
$result[$index] = null; go(function ($index, $promiseOrValue, $all, $total, &$count, $result) {
$promiseOrValue->then( if (!($promiseOrValue instanceof SwoolePromise)) {
static function ($value) use ($index, &$count, $total, &$result, $all): void { $result[$index] = $promiseOrValue;
$result[$index] = $value;
$count++; $count++;
if ($count < $total) { return;
return; }
} $result[$index] = null;
$all->resolve($result); $promiseOrValue->then(
}, static function ($value) use ($index, &$count, $total, &$result, $all): void {
[$all, 'reject'] $result[$index] = $value;
); $count++;
} else { if ($count < $total) {
$result[$index] = $promiseOrValue; return;
$count++; }
$all->resolve($result);
},
[$all, 'reject']
);
}, $index, $promiseOrValue, $all, $total, $count, $result);
} }
} }, $promisesOrValues, $all, $total, $count, $result);
if ($count === $total) { if ($count === $total) {
$all->resolve($result); $all->resolve($result);
} }

View file

@ -89,7 +89,7 @@ abstract class Migration
*/ */
public function forEachDocument(callable $callback): void public function forEachDocument(callable $callback): void
{ {
Runtime::enableCoroutine(SWOOLE_HOOK_ALL); Runtime::enableCoroutine(true, SWOOLE_HOOK_ALL);
foreach ($this->collections as $collection) { foreach ($this->collections as $collection) {
$sum = 0; $sum = 0;