diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index b51d5e7..d6f3380 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,7 +1,9 @@
name: Tests
on:
pull_request:
- push: { branches: [main] }
+ push:
+ branches:
+ - main
jobs:
tests:
@@ -12,8 +14,10 @@ jobs:
steps:
- name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
- name: Run Tests
run: |
- docker-compose up -d --build && sleep 5 && docker compose exec tests php ./vendor/bin/phpunit
\ No newline at end of file
+ docker compose up -d --build
+ sleep 5
+ docker compose exec tests php vendor/bin/phpunit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 316c952..d16782c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,3 @@
-composer.lock
/vendor/
/.idea/
*.cache
diff --git a/Dockerfile b/Dockerfile
index f62c026..bc56b8d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,24 +1,35 @@
-FROM supabase/postgres:15.1.0.96 as supabase-db
+FROM supabase/postgres:15.1.0.96 AS supabase-db
+
COPY tests/Migration/resources/supabase/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql
COPY tests/Migration/resources/supabase/2_main.sql /docker-entrypoint-initdb.d/2_main.sql
+
RUN rm -rf /docker-entrypoint-initdb.d/migrate.sh
-FROM postgres:alpine3.18 as nhost-db
+FROM postgres:alpine3.18 AS nhost-db
+
COPY tests/Migration/resources/nhost/1_globals.sql /docker-entrypoint-initdb.d/1_globals.sql
COPY tests/Migration/resources/nhost/2_main.sql /docker-entrypoint-initdb.d/2_main.sql
-FROM composer:2.0 as composer
-WORKDIR /usr/local/src/
-COPY composer.json /usr/local/src/
+FROM composer:2.0 AS composer
+
+COPY composer.json /app
+COPY composer.lock /app
+
RUN composer install --ignore-platform-reqs
-FROM php:8.1.21-fpm-alpine3.18 as tests
+FROM php:8.3.10-cli-alpine3.20 AS tests
+
# Postgres
RUN set -ex \
&& apk --no-cache add postgresql-libs postgresql-dev \
&& docker-php-ext-install pdo pdo_pgsql \
&& apk del postgresql-dev
+
COPY ./src /app/src
COPY ./tests /app/src/tests
-COPY --from=composer /usr/local/src/vendor /app/vendor
+
+COPY --from=composer /app/vendor /app/vendor
+
+WORKDIR /app
+
CMD tail -f /dev/null
diff --git a/bin/MigrationCLI.php b/bin/MigrationCLI.php
index f6024f0..68779b4 100644
--- a/bin/MigrationCLI.php
+++ b/bin/MigrationCLI.php
@@ -1,8 +1,15 @@
'databases',
+ '$id' => 'collections',
+ 'name' => 'Collections',
+ 'attributes' => [
+ [
+ '$id' => 'databaseInternalId',
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => Database::LENGTH_KEY,
+ 'signed' => true,
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => 'databaseId',
+ 'type' => Database::VAR_STRING,
+ 'signed' => true,
+ 'size' => Database::LENGTH_KEY,
+ 'format' => '',
+ 'filters' => [],
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ ],
+ [
+ '$id' => 'name',
+ 'type' => Database::VAR_STRING,
+ 'size' => Database::LENGTH_KEY,
+ 'required' => true,
+ 'signed' => true,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ [
+ '$id' => 'enabled',
+ 'type' => Database::VAR_BOOLEAN,
+ 'signed' => true,
+ 'size' => 0,
+ 'format' => '',
+ 'filters' => [],
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ ],
+ [
+ '$id' => 'documentSecurity',
+ 'type' => Database::VAR_BOOLEAN,
+ 'signed' => true,
+ 'size' => 0,
+ 'format' => '',
+ 'filters' => [],
+ 'required' => true,
+ 'default' => null,
+ 'array' => false,
+ ],
+ [
+ '$id' => 'attributes',
+ 'type' => Database::VAR_STRING,
+ 'size' => 1000000,
+ 'required' => false,
+ 'signed' => true,
+ 'array' => false,
+ 'filters' => ['subQueryAttributes'],
+ ],
+ [
+ '$id' => 'indexes',
+ 'type' => Database::VAR_STRING,
+ 'size' => 1000000,
+ 'required' => false,
+ 'signed' => true,
+ 'array' => false,
+ 'filters' => ['subQueryIndexes'],
+ ],
+ [
+ '$id' => 'search',
+ 'type' => Database::VAR_STRING,
+ 'format' => '',
+ 'size' => 16384,
+ 'signed' => true,
+ 'required' => false,
+ 'default' => null,
+ 'array' => false,
+ 'filters' => [],
+ ],
+ ],
+ 'indexes' => [
+ [
+ '$id' => '_fulltext_search',
+ 'type' => Database::INDEX_FULLTEXT,
+ 'attributes' => ['search'],
+ 'lengths' => [],
+ 'orders' => [],
+ ],
+ [
+ '$id' => '_key_name',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['name'],
+ 'lengths' => [Database::LENGTH_KEY],
+ 'orders' => [Database::ORDER_ASC],
+ ],
+ [
+ '$id' => '_key_enabled',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['enabled'],
+ 'lengths' => [],
+ 'orders' => [Database::ORDER_ASC],
+ ],
+ [
+ '$id' => '_key_documentSecurity',
+ 'type' => Database::INDEX_KEY,
+ 'attributes' => ['documentSecurity'],
+ 'lengths' => [],
+ 'orders' => [Database::ORDER_ASC],
+ ],
+ ],
+ ];
+
/**
* Prints the current status of migrations as a table after wiping the screen
*/
- public function drawFrame()
+ public function drawFrame(): void
{
- echo chr(27).chr(91).'H'.chr(27).chr(91).'J';
+ echo chr(27) . chr(91) . 'H' . chr(27) . chr(91) . 'J';
$statusCounters = $this->transfer->getStatusCounters();
@@ -42,39 +169,39 @@ public function drawFrame()
// Render Errors
$destErrors = $this->destination->getErrors();
- if (! empty($destErrors)) {
+ if (!empty($destErrors)) {
echo "\n\nDestination Errors:\n";
foreach ($destErrors as $error) {
/** @var Utopia\Migration\Exception $error */
- echo $error->getResourceName().'['.$error->getResourceId().'] - '.$error->getMessage()."\n";
+ echo $error->getResourceName() . '[' . $error->getResourceId() . '] - ' . $error->getMessage() . "\n";
}
}
$sourceErrors = $this->source->getErrors();
- if (! empty($sourceErrors)) {
+ if (!empty($sourceErrors)) {
echo "\n\nSource Errors:\n";
foreach ($sourceErrors as $error) {
/** @var Utopia\Migration\Exception $error */
- echo $error->getResourceGroup().'['.$error->getResourceId().'] - '.$error->getMessage()."\n";
+ echo $error->getResourceGroup() . '[' . $error->getResourceId() . '] - ' . $error->getMessage() . "\n";
}
}
// Render Warnings
$sourceWarnings = $this->source->getWarnings();
- if (! empty($sourceWarnings)) {
+ if (!empty($sourceWarnings)) {
echo "\n\nSource Warnings:\n";
foreach ($sourceWarnings as $warning) {
/** @var Utopia\Migration\Warning $warning */
- echo $warning->getResourceName().'['.$warning->getResourceId().'] - '.$warning->getMessage()."\n";
+ echo $warning->getResourceName() . '[' . $warning->getResourceId() . '] - ' . $warning->getMessage() . "\n";
}
}
$destWarnings = $this->destination->getWarnings();
- if (! empty($destWarnings)) {
+ if (!empty($destWarnings)) {
echo "\n\nDestination Warnings:\n";
foreach ($destWarnings as $warning) {
/** @var Utopia\Migration\Warning $warning */
- echo $warning->getResourceName().'['.$warning->getResourceId().'] - '.$warning->getMessage()."\n";
+ echo $warning->getResourceName() . '[' . $warning->getResourceId() . '] - ' . $warning->getMessage() . "\n";
}
}
}
@@ -99,7 +226,7 @@ public function getSource(): Source
);
case 'firebase':
return new Firebase(
- json_decode(file_get_contents(__DIR__.'/serviceAccount.json'), true)
+ json_decode(file_get_contents(__DIR__ . '/serviceAccount.json'), true)
);
case 'nhost':
return new NHost(
@@ -122,7 +249,9 @@ public function getDestination(): Destination
return new DestinationsAppwrite(
$_ENV['DESTINATION_APPWRITE_TEST_PROJECT'],
$_ENV['DESTINATION_APPWRITE_TEST_ENDPOINT'],
- $_ENV['DESTINATION_APPWRITE_TEST_KEY']
+ $_ENV['DESTINATION_APPWRITE_TEST_KEY'],
+ $this->getDatabase(),
+ self::STRUCTURE
);
case 'local':
return new Local('./localBackup');
@@ -131,7 +260,131 @@ public function getDestination(): Destination
}
}
- public function start()
+ public function getDatabase(): Database
+ {
+ Database::addFilter(
+ 'subQueryAttributes',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ $attributes = $database->find('attributes', [
+ Query::equal('collectionInternalId', [$document->getInternalId()]),
+ Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
+ Query::limit($database->getLimitForAttributes()),
+ ]);
+
+ foreach ($attributes as $attribute) {
+ if ($attribute->getAttribute('type') === Database::VAR_RELATIONSHIP) {
+ $options = $attribute->getAttribute('options');
+ foreach ($options as $key => $value) {
+ $attribute->setAttribute($key, $value);
+ }
+ $attribute->removeAttribute('options');
+ }
+ }
+
+ return $attributes;
+ }
+ );
+
+ Database::addFilter(
+ 'subQueryIndexes',
+ function (mixed $value) {
+ return;
+ },
+ function (mixed $value, Document $document, Database $database) {
+ return $database
+ ->find('indexes', [
+ Query::equal('collectionInternalId', [$document->getInternalId()]),
+ Query::equal('databaseInternalId', [$document->getAttribute('databaseInternalId')]),
+ Query::limit($database->getLimitForIndexes()),
+ ]);
+ }
+ );
+
+ Database::addFilter(
+ 'casting',
+ function (mixed $value) {
+ return json_encode(['value' => $value], JSON_PRESERVE_ZERO_FRACTION);
+ },
+ function (mixed $value) {
+ if (is_null($value)) {
+ return;
+ }
+
+ return json_decode($value, true)['value'];
+ }
+ );
+
+ Database::addFilter(
+ 'enum',
+ function (mixed $value, Document $attribute) {
+ if ($attribute->isSet('elements')) {
+ $attribute->removeAttribute('elements');
+ }
+
+ return $value;
+ },
+ function (mixed $value, Document $attribute) {
+ $formatOptions = \json_decode($attribute->getAttribute('formatOptions', '[]'), true);
+ if (isset($formatOptions['elements'])) {
+ $attribute->setAttribute('elements', $formatOptions['elements']);
+ }
+
+ return $value;
+ }
+ );
+
+ Database::addFilter(
+ 'range',
+ function (mixed $value, Document $attribute) {
+ if ($attribute->isSet('min')) {
+ $attribute->removeAttribute('min');
+ }
+ if ($attribute->isSet('max')) {
+ $attribute->removeAttribute('max');
+ }
+
+ return $value;
+ },
+ function (mixed $value, Document $attribute) {
+ $formatOptions = json_decode($attribute->getAttribute('formatOptions', '[]'), true);
+ if (isset($formatOptions['min']) || isset($formatOptions['max'])) {
+ $attribute
+ ->setAttribute('min', $formatOptions['min'])
+ ->setAttribute('max', $formatOptions['max'])
+ ;
+ }
+
+ return $value;
+ }
+ );
+
+ $database = new Database(
+ new MariaDB(new PDO(
+ $_ENV['DESTINATION_APPWRITE_TEST_DSN'],
+ $_ENV['DESTINATION_APPWRITE_TEST_USER'],
+ $_ENV['DESTINATION_APPWRITE_TEST_PASSWORD'],
+ [
+ PDO::ATTR_TIMEOUT => 3,
+ PDO::ATTR_PERSISTENT => true,
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
+ PDO::ATTR_EMULATE_PREPARES => true,
+ PDO::ATTR_STRINGIFY_FETCHES => true
+ ],
+ )),
+ new Cache(new None())
+ );
+
+ $database
+ ->setDatabase('appwrite')
+ ->setNamespace('_' . $_ENV['DESTINATION_APPWRITE_TEST_NAMESPACE']);
+
+ return $database;
+ }
+
+ public function start(): void
{
$dotenv = Dotenv::createImmutable(__DIR__);
$dotenv->load();
@@ -156,12 +409,12 @@ public function start()
/**
* Run Transfer
*/
- $this->transfer->run(
+ Authorization::skip(fn () => $this->transfer->run(
$this->source->getSupportedResources(),
- function (array $resources) {
+ function () {
$this->drawFrame();
}
- );
+ ));
}
}
diff --git a/composer.json b/composer.json
index ccc4648..450cd5c 100644
--- a/composer.json
+++ b/composer.json
@@ -11,20 +11,35 @@
}
},
"autoload-dev": {
- "psr-4": {"Utopia\\Tests\\": "tests/Migration"}
+ "psr-4": {
+ "Utopia\\Tests\\": "tests/Migration"
+ }
},
"scripts": {
+ "test": "./vendor/bin/phpunit",
"lint": "./vendor/bin/pint --test",
- "format": "./vendor/bin/pint"
+ "format": "./vendor/bin/pint",
+ "check": "./vendor/bin/phpstan analyse --level=8 --memory-limit 512M"
},
"require": {
- "php": "8.*",
- "appwrite/appwrite": "10.1.0"
+ "php": "8.3.*",
+ "ext-curl": "*",
+ "ext-openssl": "*",
+ "appwrite/appwrite": "11.1.*",
+ "utopia-php/database": "0.52.*",
+ "utopia-php/storage": "0.18.*",
+ "utopia-php/dsn": "0.2.*",
+ "utopia-php/framework": "0.33.*"
},
"require-dev": {
- "phpunit/phpunit": "9.*",
- "vlucas/phpdotenv": "5.*",
- "laravel/pint": "1.*",
- "utopia-php/cli": "^0.18.0"
+ "ext-pdo": "*",
+ "phpunit/phpunit": "11.2.*",
+ "vlucas/phpdotenv": "5.6.*",
+ "laravel/pint": "1.17.*",
+ "phpstan/phpstan": "1.11.*",
+ "utopia-php/cli": "0.16.*"
+ },
+ "platform": {
+ "php": "8.3"
}
}
diff --git a/composer.lock b/composer.lock
new file mode 100644
index 0000000..d37ed66
--- /dev/null
+++ b/composer.lock
@@ -0,0 +1,2830 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "09c49772fb03e8a2c305a6e24f3e2bf7",
+ "packages": [
+ {
+ "name": "appwrite/appwrite",
+ "version": "11.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/appwrite/sdk-for-php.git",
+ "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/appwrite/sdk-for-php/zipball/1d043f543acdb17b9fdb440b1b2dd208e400bad3",
+ "reference": "1d043f543acdb17b9fdb440b1b2dd208e400bad3",
+ "shasum": ""
+ },
+ "require": {
+ "ext-curl": "*",
+ "ext-json": "*",
+ "php": ">=7.1.0"
+ },
+ "require-dev": {
+ "mockery/mockery": "^1.6.6",
+ "phpunit/phpunit": "^10"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Appwrite\\": "src/Appwrite"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Appwrite is an open-source self-hosted backend server that abstract and simplify complex and repetitive development tasks behind a very simple REST API",
+ "support": {
+ "email": "team@appwrite.io",
+ "issues": "https://github.com/appwrite/sdk-for-php/issues",
+ "source": "https://github.com/appwrite/sdk-for-php/tree/11.1.0",
+ "url": "https://appwrite.io/support"
+ },
+ "time": "2024-06-26T07:03:23+00:00"
+ },
+ {
+ "name": "jean85/pretty-package-versions",
+ "version": "2.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Jean85/pretty-package-versions.git",
+ "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4",
+ "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4",
+ "shasum": ""
+ },
+ "require": {
+ "composer-runtime-api": "^2.0.0",
+ "php": "^7.1|^8.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.2",
+ "jean85/composer-provided-replaced-stub-package": "^1.0",
+ "phpstan/phpstan": "^1.4",
+ "phpunit/phpunit": "^7.5|^8.5|^9.4",
+ "vimeo/psalm": "^4.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Jean85\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Alessandro Lai",
+ "email": "alessandro.lai85@gmail.com"
+ }
+ ],
+ "description": "A library to get pretty versions strings of installed dependencies",
+ "keywords": [
+ "composer",
+ "package",
+ "release",
+ "versions"
+ ],
+ "support": {
+ "issues": "https://github.com/Jean85/pretty-package-versions/issues",
+ "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6"
+ },
+ "time": "2024-03-08T09:58:59+00:00"
+ },
+ {
+ "name": "mongodb/mongodb",
+ "version": "1.10.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/mongodb/mongo-php-library.git",
+ "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/mongodb/mongo-php-library/zipball/b0bbd657f84219212487d01a8ffe93a789e1e488",
+ "reference": "b0bbd657f84219212487d01a8ffe93a789e1e488",
+ "shasum": ""
+ },
+ "require": {
+ "ext-hash": "*",
+ "ext-json": "*",
+ "ext-mongodb": "^1.11.0",
+ "jean85/pretty-package-versions": "^1.2 || ^2.0.1",
+ "php": "^7.1 || ^8.0",
+ "symfony/polyfill-php80": "^1.19"
+ },
+ "require-dev": {
+ "doctrine/coding-standard": "^9.0",
+ "squizlabs/php_codesniffer": "^3.6",
+ "symfony/phpunit-bridge": "^5.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.10.x-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/functions.php"
+ ],
+ "psr-4": {
+ "MongoDB\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Andreas Braun",
+ "email": "andreas.braun@mongodb.com"
+ },
+ {
+ "name": "Jeremy Mikola",
+ "email": "jmikola@gmail.com"
+ }
+ ],
+ "description": "MongoDB driver library",
+ "homepage": "https://jira.mongodb.org/browse/PHPLIB",
+ "keywords": [
+ "database",
+ "driver",
+ "mongodb",
+ "persistence"
+ ],
+ "support": {
+ "issues": "https://github.com/mongodb/mongo-php-library/issues",
+ "source": "https://github.com/mongodb/mongo-php-library/tree/1.10.0"
+ },
+ "time": "2021-10-20T22:22:37+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php80",
+ "version": "v1.30.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php80.git",
+ "reference": "77fa7995ac1b21ab60769b7323d600a991a90433"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433",
+ "reference": "77fa7995ac1b21ab60769b7323d600a991a90433",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php80\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Ion Bazan",
+ "email": "ion.bazan@gmail.com"
+ },
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php80/tree/v1.30.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-31T15:07:36+00:00"
+ },
+ {
+ "name": "utopia-php/cache",
+ "version": "0.10.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/cache.git",
+ "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/cache/zipball/b22c6eb6d308de246b023efd0fc9758aee8b8247",
+ "reference": "b22c6eb6d308de246b023efd0fc9758aee8b8247",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-memcached": "*",
+ "ext-redis": "*",
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpstan/phpstan": "1.9.x-dev",
+ "phpunit/phpunit": "^9.3",
+ "vimeo/psalm": "4.13.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Cache\\": "src/Cache"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple cache library to manage application cache storing, loading and purging",
+ "keywords": [
+ "cache",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/cache/issues",
+ "source": "https://github.com/utopia-php/cache/tree/0.10.2"
+ },
+ "time": "2024-06-25T20:36:35+00:00"
+ },
+ {
+ "name": "utopia-php/database",
+ "version": "0.52.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/database.git",
+ "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/database/zipball/0b48921dd5e9e07529983f954cf987e7d4461f6e",
+ "reference": "0b48921dd5e9e07529983f954cf987e7d4461f6e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "ext-pdo": "*",
+ "php": ">=8.0",
+ "utopia-php/cache": "0.10.*",
+ "utopia-php/framework": "0.33.*",
+ "utopia-php/mongo": "0.3.*"
+ },
+ "require-dev": {
+ "fakerphp/faker": "1.23.*",
+ "laravel/pint": "1.17.*",
+ "pcov/clobber": "2.0.*",
+ "phpstan/phpstan": "1.11.*",
+ "phpunit/phpunit": "9.6.*",
+ "rregeer/phpunit-coverage-check": "0.3.*",
+ "swoole/ide-helper": "5.1.3",
+ "utopia-php/cli": "0.14.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Database\\": "src/Database"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple library to manage application persistence using multiple database adapters",
+ "keywords": [
+ "database",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/database/issues",
+ "source": "https://github.com/utopia-php/database/tree/0.52.0"
+ },
+ "time": "2024-08-21T08:11:14+00:00"
+ },
+ {
+ "name": "utopia-php/dsn",
+ "version": "0.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/dsn.git",
+ "reference": "42ee37a3d1785100b2f69091c9d4affadb6846eb"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/dsn/zipball/42ee37a3d1785100b2f69091c9d4affadb6846eb",
+ "reference": "42ee37a3d1785100b2f69091c9d4affadb6846eb",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpunit/phpunit": "^9.3",
+ "squizlabs/php_codesniffer": "^3.6",
+ "vimeo/psalm": "4.0.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\DSN\\": "src/DSN"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple library for parsing and managing Data Source Names ( DSNs )",
+ "keywords": [
+ "dsn",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/dsn/issues",
+ "source": "https://github.com/utopia-php/dsn/tree/0.2.1"
+ },
+ "time": "2024-05-07T02:01:25+00:00"
+ },
+ {
+ "name": "utopia-php/framework",
+ "version": "0.33.8",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/http.git",
+ "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/http/zipball/a7f577540a25cb90896fef2b64767bf8d700f3c5",
+ "reference": "a7f577540a25cb90896fef2b64767bf8d700f3c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "laravel/pint": "^1.2",
+ "phpbench/phpbench": "^1.2",
+ "phpstan/phpstan": "^1.10",
+ "phpunit/phpunit": "^9.5.25"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple, light and advanced PHP framework",
+ "keywords": [
+ "framework",
+ "php",
+ "upf"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/http/issues",
+ "source": "https://github.com/utopia-php/http/tree/0.33.8"
+ },
+ "time": "2024-08-15T14:10:09+00:00"
+ },
+ {
+ "name": "utopia-php/mongo",
+ "version": "0.3.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/mongo.git",
+ "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/mongo/zipball/52326a9a43e2d27ff0c15c48ba746dacbe9a7aee",
+ "reference": "52326a9a43e2d27ff0c15c48ba746dacbe9a7aee",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mongodb": "*",
+ "mongodb/mongodb": "1.10.0",
+ "php": ">=8.0"
+ },
+ "require-dev": {
+ "fakerphp/faker": "^1.14",
+ "laravel/pint": "1.2.*",
+ "phpstan/phpstan": "1.8.*",
+ "phpunit/phpunit": "^9.4",
+ "swoole/ide-helper": "4.8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Mongo\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eldad Fux",
+ "email": "eldad@appwrite.io"
+ },
+ {
+ "name": "Wess",
+ "email": "wess@appwrite.io"
+ }
+ ],
+ "description": "A simple library to manage Mongo database",
+ "keywords": [
+ "database",
+ "mongo",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/mongo/issues",
+ "source": "https://github.com/utopia-php/mongo/tree/0.3.1"
+ },
+ "time": "2023-09-01T17:25:28+00:00"
+ },
+ {
+ "name": "utopia-php/storage",
+ "version": "0.18.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/storage.git",
+ "reference": "94ab8758fabcefee5c5fa723616e45719833f922"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/storage/zipball/94ab8758fabcefee5c5fa723616e45719833f922",
+ "reference": "94ab8758fabcefee5c5fa723616e45719833f922",
+ "shasum": ""
+ },
+ "require": {
+ "ext-brotli": "*",
+ "ext-fileinfo": "*",
+ "ext-lz4": "*",
+ "ext-snappy": "*",
+ "ext-xz": "*",
+ "ext-zlib": "*",
+ "ext-zstd": "*",
+ "php": ">=8.0",
+ "utopia-php/framework": "0.*.*",
+ "utopia-php/system": "0.*.*"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpunit/phpunit": "^9.3",
+ "vimeo/psalm": "4.0.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\Storage\\": "src/Storage"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple Storage library to manage application storage",
+ "keywords": [
+ "framework",
+ "php",
+ "storage",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/storage/issues",
+ "source": "https://github.com/utopia-php/storage/tree/0.18.4"
+ },
+ "time": "2024-04-02T08:24:09+00:00"
+ },
+ {
+ "name": "utopia-php/system",
+ "version": "0.8.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/system.git",
+ "reference": "a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/system/zipball/a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e",
+ "reference": "a2cbfb3c69b9ecb8b6f06c5774f3cf279ea7665e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.0.0"
+ },
+ "require-dev": {
+ "laravel/pint": "1.13.*",
+ "phpstan/phpstan": "1.10.*",
+ "phpunit/phpunit": "9.6.*"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\System\\": "src/System"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Eldad Fux",
+ "email": "eldad@appwrite.io"
+ },
+ {
+ "name": "Torsten Dittmann",
+ "email": "torsten@appwrite.io"
+ }
+ ],
+ "description": "A simple library for obtaining information about the host's system.",
+ "keywords": [
+ "framework",
+ "php",
+ "system",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/system/issues",
+ "source": "https://github.com/utopia-php/system/tree/0.8.0"
+ },
+ "time": "2024-04-01T10:22:28+00:00"
+ }
+ ],
+ "packages-dev": [
+ {
+ "name": "graham-campbell/result-type",
+ "version": "v1.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/GrahamCampbell/Result-Type.git",
+ "reference": "3ba905c11371512af9d9bdd27d99b782216b6945"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/3ba905c11371512af9d9bdd27d99b782216b6945",
+ "reference": "3ba905c11371512af9d9bdd27d99b782216b6945",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "GrahamCampbell\\ResultType\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "An Implementation Of The Result Type",
+ "keywords": [
+ "Graham Campbell",
+ "GrahamCampbell",
+ "Result Type",
+ "Result-Type",
+ "result"
+ ],
+ "support": {
+ "issues": "https://github.com/GrahamCampbell/Result-Type/issues",
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/graham-campbell/result-type",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-20T21:45:45+00:00"
+ },
+ {
+ "name": "laravel/pint",
+ "version": "v1.17.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/laravel/pint.git",
+ "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/laravel/pint/zipball/e8a88130a25e3f9d4d5785e6a1afca98268ab110",
+ "reference": "e8a88130a25e3f9d4d5785e6a1afca98268ab110",
+ "shasum": ""
+ },
+ "require": {
+ "ext-json": "*",
+ "ext-mbstring": "*",
+ "ext-tokenizer": "*",
+ "ext-xml": "*",
+ "php": "^8.1.0"
+ },
+ "require-dev": {
+ "friendsofphp/php-cs-fixer": "^3.61.1",
+ "illuminate/view": "^10.48.18",
+ "larastan/larastan": "^2.9.8",
+ "laravel-zero/framework": "^10.4.0",
+ "mockery/mockery": "^1.6.12",
+ "nunomaduro/termwind": "^1.15.1",
+ "pestphp/pest": "^2.35.0"
+ },
+ "bin": [
+ "builds/pint"
+ ],
+ "type": "project",
+ "autoload": {
+ "psr-4": {
+ "App\\": "app/",
+ "Database\\Seeders\\": "database/seeders/",
+ "Database\\Factories\\": "database/factories/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nuno Maduro",
+ "email": "enunomaduro@gmail.com"
+ }
+ ],
+ "description": "An opinionated code formatter for PHP.",
+ "homepage": "https://laravel.com",
+ "keywords": [
+ "format",
+ "formatter",
+ "lint",
+ "linter",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/laravel/pint/issues",
+ "source": "https://github.com/laravel/pint"
+ },
+ "time": "2024-08-06T15:11:54+00:00"
+ },
+ {
+ "name": "myclabs/deep-copy",
+ "version": "1.12.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/myclabs/DeepCopy.git",
+ "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
+ "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.1 || ^8.0"
+ },
+ "conflict": {
+ "doctrine/collections": "<1.6.8",
+ "doctrine/common": "<2.13.3 || >=3 <3.2.2"
+ },
+ "require-dev": {
+ "doctrine/collections": "^1.6.8",
+ "doctrine/common": "^2.13.3 || ^3.2.2",
+ "phpspec/prophecy": "^1.10",
+ "phpunit/phpunit": "^7.5.20 || ^8.5.23 || ^9.5.13"
+ },
+ "type": "library",
+ "autoload": {
+ "files": [
+ "src/DeepCopy/deep_copy.php"
+ ],
+ "psr-4": {
+ "DeepCopy\\": "src/DeepCopy/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Create deep copies (clones) of your objects",
+ "keywords": [
+ "clone",
+ "copy",
+ "duplicate",
+ "object",
+ "object graph"
+ ],
+ "support": {
+ "issues": "https://github.com/myclabs/DeepCopy/issues",
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-06-12T14:39:25+00:00"
+ },
+ {
+ "name": "nikic/php-parser",
+ "version": "v5.1.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/nikic/PHP-Parser.git",
+ "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
+ "shasum": ""
+ },
+ "require": {
+ "ext-ctype": "*",
+ "ext-json": "*",
+ "ext-tokenizer": "*",
+ "php": ">=7.4"
+ },
+ "require-dev": {
+ "ircmaxell/php-yacc": "^0.0.7",
+ "phpunit/phpunit": "^9.0"
+ },
+ "bin": [
+ "bin/php-parse"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpParser\\": "lib/PhpParser"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Nikita Popov"
+ }
+ ],
+ "description": "A PHP parser written in PHP",
+ "keywords": [
+ "parser",
+ "php"
+ ],
+ "support": {
+ "issues": "https://github.com/nikic/PHP-Parser/issues",
+ "source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
+ },
+ "time": "2024-07-01T20:03:41+00:00"
+ },
+ {
+ "name": "phar-io/manifest",
+ "version": "2.0.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/manifest.git",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
+ "reference": "54750ef60c58e43759730615a392c31c80e23176",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-phar": "*",
+ "ext-xmlwriter": "*",
+ "phar-io/version": "^3.0.1",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
+ "support": {
+ "issues": "https://github.com/phar-io/manifest/issues",
+ "source": "https://github.com/phar-io/manifest/tree/2.0.4"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:33:53+00:00"
+ },
+ {
+ "name": "phar-io/version",
+ "version": "3.2.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phar-io/version.git",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phar-io/version/zipball/4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "reference": "4f7fd7836c6f332bb2933569e566a0d6c4cbed74",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Heuer",
+ "email": "sebastian@phpeople.de",
+ "role": "Developer"
+ },
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "Library for handling version information and constraints",
+ "support": {
+ "issues": "https://github.com/phar-io/version/issues",
+ "source": "https://github.com/phar-io/version/tree/3.2.1"
+ },
+ "time": "2022-02-21T01:04:05+00:00"
+ },
+ {
+ "name": "phpoption/phpoption",
+ "version": "1.9.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/schmittjoh/php-option.git",
+ "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/e3fac8b24f56113f7cb96af14958c0dd16330f54",
+ "reference": "e3fac8b24f56113f7cb96af14958c0dd16330f54",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2.5 || ^8.0"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.39 || ^9.6.20 || ^10.5.28"
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ },
+ "branch-alias": {
+ "dev-master": "1.9-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PhpOption\\": "src/PhpOption/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "Apache-2.0"
+ ],
+ "authors": [
+ {
+ "name": "Johannes M. Schmitt",
+ "email": "schmittjoh@gmail.com",
+ "homepage": "https://github.com/schmittjoh"
+ },
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ }
+ ],
+ "description": "Option Type for PHP",
+ "keywords": [
+ "language",
+ "option",
+ "php",
+ "type"
+ ],
+ "support": {
+ "issues": "https://github.com/schmittjoh/php-option/issues",
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpoption/phpoption",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-20T21:41:07+00:00"
+ },
+ {
+ "name": "phpstan/phpstan",
+ "version": "1.11.11",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpstan/phpstan.git",
+ "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpstan/phpstan/zipball/707c2aed5d8d0075666e673a5e71440c1d01a5a3",
+ "reference": "707c2aed5d8d0075666e673a5e71440c1d01a5a3",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.2|^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan-shim": "*"
+ },
+ "bin": [
+ "phpstan",
+ "phpstan.phar"
+ ],
+ "type": "library",
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "PHPStan - PHP Static Analysis Tool",
+ "keywords": [
+ "dev",
+ "static analysis"
+ ],
+ "support": {
+ "docs": "https://phpstan.org/user-guide/getting-started",
+ "forum": "https://github.com/phpstan/phpstan/discussions",
+ "issues": "https://github.com/phpstan/phpstan/issues",
+ "security": "https://github.com/phpstan/phpstan/security/policy",
+ "source": "https://github.com/phpstan/phpstan-src"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/ondrejmirtes",
+ "type": "github"
+ },
+ {
+ "url": "https://github.com/phpstan",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-19T14:37:29+00:00"
+ },
+ {
+ "name": "phpunit/php-code-coverage",
+ "version": "11.0.6",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-code-coverage.git",
+ "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
+ "reference": "ebdffc9e09585dafa71b9bffcdb0a229d4704c45",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-libxml": "*",
+ "ext-xmlwriter": "*",
+ "nikic/php-parser": "^5.1.0",
+ "php": ">=8.2",
+ "phpunit/php-file-iterator": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "sebastian/code-unit-reverse-lookup": "^4.0.1",
+ "sebastian/complexity": "^4.0.1",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/lines-of-code": "^3.0.1",
+ "sebastian/version": "^5.0.1",
+ "theseer/tokenizer": "^1.2.3"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcov": "PHP extension that provides line coverage",
+ "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.0.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.",
+ "homepage": "https://github.com/sebastianbergmann/php-code-coverage",
+ "keywords": [
+ "coverage",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
+ "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.6"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-22T04:37:56+00:00"
+ },
+ {
+ "name": "phpunit/php-file-iterator",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-file-iterator.git",
+ "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6ed896bf50bbbfe4d504a33ed5886278c78e4a26",
+ "reference": "6ed896bf50bbbfe4d504a33ed5886278c78e4a26",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "FilterIterator implementation that filters files based on a list of suffixes.",
+ "homepage": "https://github.com/sebastianbergmann/php-file-iterator/",
+ "keywords": [
+ "filesystem",
+ "iterator"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues",
+ "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:06:37+00:00"
+ },
+ {
+ "name": "phpunit/php-invoker",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-invoker.git",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "ext-pcntl": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-pcntl": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Invoke callables with a timeout",
+ "homepage": "https://github.com/sebastianbergmann/php-invoker/",
+ "keywords": [
+ "process"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-invoker/issues",
+ "security": "https://github.com/sebastianbergmann/php-invoker/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:07:44+00:00"
+ },
+ {
+ "name": "phpunit/php-text-template",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-text-template.git",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Simple template engine.",
+ "homepage": "https://github.com/sebastianbergmann/php-text-template/",
+ "keywords": [
+ "template"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-text-template/issues",
+ "security": "https://github.com/sebastianbergmann/php-text-template/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:08:43+00:00"
+ },
+ {
+ "name": "phpunit/php-timer",
+ "version": "7.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/php-timer.git",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Utility class for timing",
+ "homepage": "https://github.com/sebastianbergmann/php-timer/",
+ "keywords": [
+ "timer"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/php-timer/issues",
+ "security": "https://github.com/sebastianbergmann/php-timer/security/policy",
+ "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:09:35+00:00"
+ },
+ {
+ "name": "phpunit/phpunit",
+ "version": "11.2.9",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/phpunit.git",
+ "reference": "c197bbaaca360efda351369bf1fd9cc1ca6bcbf7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c197bbaaca360efda351369bf1fd9cc1ca6bcbf7",
+ "reference": "c197bbaaca360efda351369bf1fd9cc1ca6bcbf7",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-json": "*",
+ "ext-libxml": "*",
+ "ext-mbstring": "*",
+ "ext-xml": "*",
+ "ext-xmlwriter": "*",
+ "myclabs/deep-copy": "^1.12.0",
+ "phar-io/manifest": "^2.0.4",
+ "phar-io/version": "^3.2.1",
+ "php": ">=8.2",
+ "phpunit/php-code-coverage": "^11.0.5",
+ "phpunit/php-file-iterator": "^5.0.1",
+ "phpunit/php-invoker": "^5.0.1",
+ "phpunit/php-text-template": "^4.0.1",
+ "phpunit/php-timer": "^7.0.1",
+ "sebastian/cli-parser": "^3.0.2",
+ "sebastian/code-unit": "^3.0.1",
+ "sebastian/comparator": "^6.0.1",
+ "sebastian/diff": "^6.0.2",
+ "sebastian/environment": "^7.2.0",
+ "sebastian/exporter": "^6.1.3",
+ "sebastian/global-state": "^7.0.2",
+ "sebastian/object-enumerator": "^6.0.1",
+ "sebastian/type": "^5.0.1",
+ "sebastian/version": "^5.0.1"
+ },
+ "suggest": {
+ "ext-soap": "To be able to generate mocks based on WSDL files"
+ },
+ "bin": [
+ "phpunit"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "11.2-dev"
+ }
+ },
+ "autoload": {
+ "files": [
+ "src/Framework/Assert/Functions.php"
+ ],
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "The PHP Unit Testing framework.",
+ "homepage": "https://phpunit.de/",
+ "keywords": [
+ "phpunit",
+ "testing",
+ "xunit"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/phpunit/issues",
+ "security": "https://github.com/sebastianbergmann/phpunit/security/policy",
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/11.2.9"
+ },
+ "funding": [
+ {
+ "url": "https://phpunit.de/sponsors.html",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpunit/phpunit",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-30T11:09:23+00:00"
+ },
+ {
+ "name": "sebastian/cli-parser",
+ "version": "3.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/cli-parser.git",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for parsing CLI options",
+ "homepage": "https://github.com/sebastianbergmann/cli-parser",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/cli-parser/issues",
+ "security": "https://github.com/sebastianbergmann/cli-parser/security/policy",
+ "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:41:36+00:00"
+ },
+ {
+ "name": "sebastian/code-unit",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit.git",
+ "reference": "6bb7d09d6623567178cf54126afa9c2310114268"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268",
+ "reference": "6bb7d09d6623567178cf54126afa9c2310114268",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/code-unit",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:44:28+00:00"
+ },
+ {
+ "name": "sebastian/code-unit-reverse-lookup",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e",
+ "reference": "183a9b2632194febd219bb9246eee421dad8d45e",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Looks up which function or method a line of code belongs to",
+ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues",
+ "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy",
+ "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:45:54+00:00"
+ },
+ {
+ "name": "sebastian/comparator",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/comparator.git",
+ "reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/450d8f237bd611c45b5acf0733ce43e6bb280f81",
+ "reference": "450d8f237bd611c45b5acf0733ce43e6bb280f81",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/diff": "^6.0",
+ "sebastian/exporter": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@2bepublished.at"
+ }
+ ],
+ "description": "Provides the functionality to compare PHP values for equality",
+ "homepage": "https://github.com/sebastianbergmann/comparator",
+ "keywords": [
+ "comparator",
+ "compare",
+ "equality"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/comparator/issues",
+ "security": "https://github.com/sebastianbergmann/comparator/security/policy",
+ "source": "https://github.com/sebastianbergmann/comparator/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-08-12T06:07:25+00:00"
+ },
+ {
+ "name": "sebastian/complexity",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/complexity.git",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0",
+ "reference": "ee41d384ab1906c68852636b6de493846e13e5a0",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for calculating the complexity of PHP code units",
+ "homepage": "https://github.com/sebastianbergmann/complexity",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/complexity/issues",
+ "security": "https://github.com/sebastianbergmann/complexity/security/policy",
+ "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:49:50+00:00"
+ },
+ {
+ "name": "sebastian/diff",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/diff.git",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0",
+ "symfony/process": "^4.2 || ^5"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Kore Nordmann",
+ "email": "mail@kore-nordmann.de"
+ }
+ ],
+ "description": "Diff implementation",
+ "homepage": "https://github.com/sebastianbergmann/diff",
+ "keywords": [
+ "diff",
+ "udiff",
+ "unidiff",
+ "unified diff"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/diff/issues",
+ "security": "https://github.com/sebastianbergmann/diff/security/policy",
+ "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:53:05+00:00"
+ },
+ {
+ "name": "sebastian/environment",
+ "version": "7.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/environment.git",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "suggest": {
+ "ext-posix": "*"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.2-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Provides functionality to handle HHVM/PHP environments",
+ "homepage": "https://github.com/sebastianbergmann/environment",
+ "keywords": [
+ "Xdebug",
+ "environment",
+ "hhvm"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/environment/issues",
+ "security": "https://github.com/sebastianbergmann/environment/security/policy",
+ "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:54:44+00:00"
+ },
+ {
+ "name": "sebastian/exporter",
+ "version": "6.1.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/exporter.git",
+ "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
+ "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e",
+ "shasum": ""
+ },
+ "require": {
+ "ext-mbstring": "*",
+ "php": ">=8.2",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.1-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Volker Dusch",
+ "email": "github@wallbash.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ },
+ {
+ "name": "Bernhard Schussek",
+ "email": "bschussek@gmail.com"
+ }
+ ],
+ "description": "Provides the functionality to export PHP variables for visualization",
+ "homepage": "https://www.github.com/sebastianbergmann/exporter",
+ "keywords": [
+ "export",
+ "exporter"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/exporter/issues",
+ "security": "https://github.com/sebastianbergmann/exporter/security/policy",
+ "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:56:19+00:00"
+ },
+ {
+ "name": "sebastian/global-state",
+ "version": "7.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/global-state.git",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7",
+ "reference": "3be331570a721f9a4b5917f4209773de17f747d7",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "ext-dom": "*",
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "7.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Snapshotting of global state",
+ "homepage": "https://www.github.com/sebastianbergmann/global-state",
+ "keywords": [
+ "global state"
+ ],
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/global-state/issues",
+ "security": "https://github.com/sebastianbergmann/global-state/security/policy",
+ "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:57:36+00:00"
+ },
+ {
+ "name": "sebastian/lines-of-code",
+ "version": "3.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/lines-of-code.git",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a",
+ "shasum": ""
+ },
+ "require": {
+ "nikic/php-parser": "^5.0",
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library for counting the lines of code in PHP source code",
+ "homepage": "https://github.com/sebastianbergmann/lines-of-code",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
+ "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy",
+ "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T04:58:38+00:00"
+ },
+ {
+ "name": "sebastian/object-enumerator",
+ "version": "6.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-enumerator.git",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa",
+ "reference": "f5b498e631a74204185071eb41f33f38d64608aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2",
+ "sebastian/object-reflector": "^4.0",
+ "sebastian/recursion-context": "^6.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Traverses array structures and object graphs to enumerate all referenced objects",
+ "homepage": "https://github.com/sebastianbergmann/object-enumerator/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-enumerator/issues",
+ "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:00:13+00:00"
+ },
+ {
+ "name": "sebastian/object-reflector",
+ "version": "4.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/object-reflector.git",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "4.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ }
+ ],
+ "description": "Allows reflection of object attributes, including inherited and non-public ones",
+ "homepage": "https://github.com/sebastianbergmann/object-reflector/",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/object-reflector/issues",
+ "security": "https://github.com/sebastianbergmann/object-reflector/security/policy",
+ "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:01:32+00:00"
+ },
+ {
+ "name": "sebastian/recursion-context",
+ "version": "6.0.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/recursion-context.git",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16",
+ "reference": "694d156164372abbd149a4b85ccda2e4670c0e16",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "6.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de"
+ },
+ {
+ "name": "Jeff Welch",
+ "email": "whatthejeff@gmail.com"
+ },
+ {
+ "name": "Adam Harvey",
+ "email": "aharvey@php.net"
+ }
+ ],
+ "description": "Provides functionality to recursively process PHP variables",
+ "homepage": "https://github.com/sebastianbergmann/recursion-context",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/recursion-context/issues",
+ "security": "https://github.com/sebastianbergmann/recursion-context/security/policy",
+ "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:10:34+00:00"
+ },
+ {
+ "name": "sebastian/type",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/type.git",
+ "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb6a6566f9589e86661291d13eba708cce5eb4aa",
+ "reference": "fb6a6566f9589e86661291d13eba708cce5eb4aa",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^11.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Collection of value objects that represent the types of the PHP type system",
+ "homepage": "https://github.com/sebastianbergmann/type",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/type/issues",
+ "security": "https://github.com/sebastianbergmann/type/security/policy",
+ "source": "https://github.com/sebastianbergmann/type/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:11:49+00:00"
+ },
+ {
+ "name": "sebastian/version",
+ "version": "5.0.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/sebastianbergmann/version.git",
+ "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/45c9debb7d039ce9b97de2f749c2cf5832a06ac4",
+ "reference": "45c9debb7d039ce9b97de2f749c2cf5832a06ac4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=8.2"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "5.0-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Sebastian Bergmann",
+ "email": "sebastian@phpunit.de",
+ "role": "lead"
+ }
+ ],
+ "description": "Library that helps with managing the version number of Git-hosted PHP projects",
+ "homepage": "https://github.com/sebastianbergmann/version",
+ "support": {
+ "issues": "https://github.com/sebastianbergmann/version/issues",
+ "security": "https://github.com/sebastianbergmann/version/security/policy",
+ "source": "https://github.com/sebastianbergmann/version/tree/5.0.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/sebastianbergmann",
+ "type": "github"
+ }
+ ],
+ "time": "2024-07-03T05:13:08+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.30.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "0424dff1c58f028c451efff2045f5d92410bd540"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540",
+ "reference": "0424dff1c58f028c451efff2045f5d92410bd540",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.30.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-31T15:07:36+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.30.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c",
+ "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.1"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "name": "symfony/polyfill",
+ "url": "https://github.com/symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.30.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-06-19T12:30:46+00:00"
+ },
+ {
+ "name": "theseer/tokenizer",
+ "version": "1.2.3",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/theseer/tokenizer.git",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-dom": "*",
+ "ext-tokenizer": "*",
+ "ext-xmlwriter": "*",
+ "php": "^7.2 || ^8.0"
+ },
+ "type": "library",
+ "autoload": {
+ "classmap": [
+ "src/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Arne Blankerts",
+ "email": "arne@blankerts.de",
+ "role": "Developer"
+ }
+ ],
+ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
+ "support": {
+ "issues": "https://github.com/theseer/tokenizer/issues",
+ "source": "https://github.com/theseer/tokenizer/tree/1.2.3"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/theseer",
+ "type": "github"
+ }
+ ],
+ "time": "2024-03-03T12:36:25+00:00"
+ },
+ {
+ "name": "utopia-php/cli",
+ "version": "0.16.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/utopia-php/cli.git",
+ "reference": "5b936638c90c86d1bae83d0dbe81fe14d12ff8ff"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/utopia-php/cli/zipball/5b936638c90c86d1bae83d0dbe81fe14d12ff8ff",
+ "reference": "5b936638c90c86d1bae83d0dbe81fe14d12ff8ff",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.4",
+ "utopia-php/framework": "0.*.*"
+ },
+ "require-dev": {
+ "laravel/pint": "1.2.*",
+ "phpunit/phpunit": "^9.3",
+ "squizlabs/php_codesniffer": "^3.6",
+ "vimeo/psalm": "4.0.1"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Utopia\\CLI\\": "src/CLI"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "A simple CLI library to manage command line applications",
+ "keywords": [
+ "cli",
+ "command line",
+ "framework",
+ "php",
+ "upf",
+ "utopia"
+ ],
+ "support": {
+ "issues": "https://github.com/utopia-php/cli/issues",
+ "source": "https://github.com/utopia-php/cli/tree/0.16.0"
+ },
+ "time": "2023-08-05T13:13:08+00:00"
+ },
+ {
+ "name": "vlucas/phpdotenv",
+ "version": "v5.6.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/vlucas/phpdotenv.git",
+ "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/a59a13791077fe3d44f90e7133eb68e7d22eaff2",
+ "reference": "a59a13791077fe3d44f90e7133eb68e7d22eaff2",
+ "shasum": ""
+ },
+ "require": {
+ "ext-pcre": "*",
+ "graham-campbell/result-type": "^1.1.3",
+ "php": "^7.2.5 || ^8.0",
+ "phpoption/phpoption": "^1.9.3",
+ "symfony/polyfill-ctype": "^1.24",
+ "symfony/polyfill-mbstring": "^1.24",
+ "symfony/polyfill-php80": "^1.24"
+ },
+ "require-dev": {
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "ext-filter": "*",
+ "phpunit/phpunit": "^8.5.34 || ^9.6.13 || ^10.4.2"
+ },
+ "suggest": {
+ "ext-filter": "Required to use the boolean validator."
+ },
+ "type": "library",
+ "extra": {
+ "bamarni-bin": {
+ "bin-links": true,
+ "forward-command": false
+ },
+ "branch-alias": {
+ "dev-master": "5.6-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dotenv\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Graham Campbell",
+ "email": "hello@gjcampbell.co.uk",
+ "homepage": "https://github.com/GrahamCampbell"
+ },
+ {
+ "name": "Vance Lucas",
+ "email": "vance@vancelucas.com",
+ "homepage": "https://github.com/vlucas"
+ }
+ ],
+ "description": "Loads environment variables from `.env` to `getenv()`, `$_ENV` and `$_SERVER` automagically.",
+ "keywords": [
+ "dotenv",
+ "env",
+ "environment"
+ ],
+ "support": {
+ "issues": "https://github.com/vlucas/phpdotenv/issues",
+ "source": "https://github.com/vlucas/phpdotenv/tree/v5.6.1"
+ },
+ "funding": [
+ {
+ "url": "https://github.com/GrahamCampbell",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/vlucas/phpdotenv",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-07-20T21:52:34+00:00"
+ }
+ ],
+ "aliases": [],
+ "minimum-stability": "stable",
+ "stability-flags": [],
+ "prefer-stable": false,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "8.3.*",
+ "ext-curl": "*",
+ "ext-openssl": "*"
+ },
+ "platform-dev": {
+ "ext-pdo": "*"
+ },
+ "plugin-api-version": "2.6.0"
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index a151232..2d7c30d 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,5 +1,3 @@
-version: '3'
-
services:
supabase-db:
build:
@@ -46,14 +44,12 @@ services:
tests:
build:
context: .
- target: tests
networks:
- tests
volumes:
- - ./tests:/app/tests
- ./src:/app/src
+ - ./tests:/app/tests
- ./phpunit.xml:/app/phpunit.xml
- working_dir: /app
depends_on:
- supabase-db
- nhost-db
diff --git a/phpcs.xml b/phpcs.xml
deleted file mode 100644
index 89c508c..0000000
--- a/phpcs.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
- ./src
- ./tests
-
-
-
- *
-
-
-
- *
-
-
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index 9a35000..fad0dec 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -1,16 +1,17 @@
-
+
+
./tests/Migration/E2E
+
+ ./tests/Migration/Unit
+
diff --git a/pint.json b/pint.json
new file mode 100644
index 0000000..288cbb8
--- /dev/null
+++ b/pint.json
@@ -0,0 +1,17 @@
+{
+ "preset": "psr12",
+ "exclude": [],
+ "rules": {
+ "array_indentation": true,
+ "single_import_per_statement": true,
+ "simplified_null_return": true,
+ "ordered_imports": {
+ "sort_algorithm": "alpha",
+ "imports_order": [
+ "const",
+ "class",
+ "function"
+ ]
+ }
+ }
+}
diff --git a/src/Migration/Cache.php b/src/Migration/Cache.php
index cc0ce77..e9d2de1 100644
--- a/src/Migration/Cache.php
+++ b/src/Migration/Cache.php
@@ -2,6 +2,7 @@
namespace Utopia\Migration;
+use Utopia\Migration\Resources\Functions\Deployment;
use Utopia\Migration\Resources\Storage\File;
/**
@@ -11,7 +12,10 @@
*/
class Cache
{
- protected $cache = [];
+ /**
+ * @var array> $cache
+ */
+ protected array $cache = [];
public function __construct()
{
@@ -23,15 +27,14 @@ public function __construct()
*
* Places the resource in the cache, in the cache backend this also gets assigned a unique ID.
*
- * @param resource $resource
- * @return void
*/
- public function add($resource)
+ public function add(Resource $resource): void
{
if (! $resource->getInternalId()) {
$resourceId = uniqid();
if (isset($this->cache[$resource->getName()][$resourceId])) {
$resourceId = uniqid();
+ // todo: $resourceId is not used?
}
$resource->setInternalId(uniqid());
}
@@ -47,10 +50,10 @@ public function add($resource)
/**
* Add All Resources
*
- * @param resource[] $resources
+ * @param array $resources
* @return void
*/
- public function addAll(array $resources)
+ public function addAll(array $resources): void
{
foreach ($resources as $resource) {
$this->add($resource);
@@ -63,10 +66,10 @@ public function addAll(array $resources)
* Updates the resource in the cache, if the resource does not exist in the cache an exception is thrown.
* Use Add to add a new resource to the cache.
*
- * @param resource $resource
+ * @param Resource $resource
* @return void
*/
- public function update($resource)
+ public function update(Resource $resource): void
{
if (! in_array($resource->getName(), $this->cache)) {
$this->add($resource);
@@ -75,7 +78,11 @@ public function update($resource)
$this->cache[$resource->getName()][$resource->getInternalId()] = $resource;
}
- public function updateAll($resources)
+ /**
+ * @param array $resources
+ * @return void
+ */
+ public function updateAll(array $resources): void
{
foreach ($resources as $resource) {
$this->update($resource);
@@ -87,10 +94,11 @@ public function updateAll($resources)
*
* Removes the resource from the cache, if the resource does not exist in the cache an exception is thrown.
*
- * @param resource $resource
+ * @param Resource $resource
* @return void
+ * @throws \Exception
*/
- public function remove($resource)
+ public function remove(Resource $resource): void
{
if (! in_array($resource, $this->cache[$resource->getName()])) {
throw new \Exception('Resource does not exist in cache');
@@ -102,10 +110,10 @@ public function remove($resource)
/**
* Get Resources
*
- * @param string|resource $resourceType
- * @return resource[]
+ * @param string|Resource $resource
+ * @return array
*/
- public function get($resource)
+ public function get(string|Resource $resource): array
{
if (is_string($resource)) {
return $this->cache[$resource] ?? [];
@@ -117,9 +125,9 @@ public function get($resource)
/**
* Get All Resources
*
- * @return array
+ * @return array>
*/
- public function getAll()
+ public function getAll(): array
{
return $this->cache;
}
@@ -131,7 +139,7 @@ public function getAll()
*
* @return void
*/
- public function wipe()
+ public function wipe(): void
{
$this->cache = [];
}
diff --git a/src/Migration/Destination.php b/src/Migration/Destination.php
index bde243e..a20919e 100644
--- a/src/Migration/Destination.php
+++ b/src/Migration/Destination.php
@@ -9,17 +9,11 @@ abstract class Destination extends Target
*/
protected Source $source;
- /**
- * Get Source
- */
public function getSource(): Source
{
return $this->source;
}
- /**
- * Set Soruce
- */
public function setSource(Source $source): self
{
$this->source = $source;
@@ -30,20 +24,30 @@ public function setSource(Source $source): self
/**
* Transfer Resources to Destination from Source callback
*
- * @param string[] $resources Resources to transfer
- * @param callable $callback Callback to run after transfer
+ * @param array $resources Resources to transfer
+ * @param callable $callback Callback to run after transfer
+ * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource
*/
- public function run(array $resources, callable $callback): void
- {
- $this->source->run($resources, function (array $resources) use ($callback) {
- $this->import($resources, $callback);
- });
+ public function run(
+ array $resources,
+ callable $callback,
+ string $rootResourceId = '',
+ string $rootResourceType = '',
+ ): void {
+ $this->source->run(
+ $resources,
+ function (array $resources) use ($callback) {
+ $this->import($resources, $callback);
+ },
+ $rootResourceId,
+ $rootResourceType,
+ );
}
/**
* Import Resources
*
- * @param resource[] $resources Resources to import
+ * @param Resource[] $resources Resources to import
* @param callable $callback Callback to run after import
*/
abstract protected function import(array $resources, callable $callback): void;
diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php
index 452cc36..05d71cd 100644
--- a/src/Migration/Destinations/Appwrite.php
+++ b/src/Migration/Destinations/Appwrite.php
@@ -2,13 +2,29 @@
namespace Utopia\Migration\Destinations;
+use Appwrite\AppwriteException;
use Appwrite\Client;
+use Appwrite\Enums\Compression;
+use Appwrite\Enums\PasswordHash;
+use Appwrite\Enums\Runtime;
use Appwrite\InputFile;
-use Appwrite\Services\Databases;
use Appwrite\Services\Functions;
use Appwrite\Services\Storage;
use Appwrite\Services\Teams;
use Appwrite\Services\Users;
+use Override;
+use Utopia\Database\Database as UtopiaDatabase;
+use Utopia\Database\Document as UtopiaDocument;
+use Utopia\Database\Exception as DatabaseException;
+use Utopia\Database\Exception\Authorization as AuthorizationException;
+use Utopia\Database\Exception\Duplicate as DuplicateException;
+use Utopia\Database\Exception\Limit as LimitException;
+use Utopia\Database\Exception\Structure as StructureException;
+use Utopia\Database\Helpers\ID;
+use Utopia\Database\Helpers\Permission;
+use Utopia\Database\Query;
+use Utopia\Database\Validator\Index as IndexValidator;
+use Utopia\Database\Validator\Structure;
use Utopia\Migration\Destination;
use Utopia\Migration\Exception;
use Utopia\Migration\Resource;
@@ -17,34 +33,49 @@
use Utopia\Migration\Resources\Auth\Team;
use Utopia\Migration\Resources\Auth\User;
use Utopia\Migration\Resources\Database\Attribute;
-use Utopia\Migration\Resources\Database\Attributes\DateTime;
-use Utopia\Migration\Resources\Database\Attributes\Decimal;
-use Utopia\Migration\Resources\Database\Attributes\Email;
-use Utopia\Migration\Resources\Database\Attributes\Enum;
-use Utopia\Migration\Resources\Database\Attributes\IP;
-use Utopia\Migration\Resources\Database\Attributes\Relationship;
-use Utopia\Migration\Resources\Database\Attributes\Text;
use Utopia\Migration\Resources\Database\Collection;
use Utopia\Migration\Resources\Database\Database;
use Utopia\Migration\Resources\Database\Document;
+use Utopia\Migration\Resources\Database\Index;
use Utopia\Migration\Resources\Functions\Deployment;
use Utopia\Migration\Resources\Functions\EnvVar;
use Utopia\Migration\Resources\Functions\Func;
use Utopia\Migration\Resources\Storage\Bucket;
use Utopia\Migration\Resources\Storage\File;
-use Utopia\Migration\Resources\Storage\Index;
use Utopia\Migration\Transfer;
class Appwrite extends Destination
{
protected Client $client;
-
protected string $project;
protected string $key;
- public function __construct(string $project, string $endpoint, string $key)
- {
+
+ private Functions $functions;
+ private Storage $storage;
+ private Teams $teams;
+ private Users $users;
+
+ /**
+ * @var array
+ */
+ private array $documentBuffer = [];
+
+ /**
+ * @param string $project
+ * @param string $endpoint
+ * @param string $key
+ * @param UtopiaDatabase $database
+ * @param array> $collectionStructure
+ */
+ public function __construct(
+ string $project,
+ string $endpoint,
+ string $key,
+ protected UtopiaDatabase $database,
+ protected array $collectionStructure
+ ) {
$this->project = $project;
$this->endpoint = $endpoint;
$this->key = $key;
@@ -53,6 +84,11 @@ public function __construct(string $project, string $endpoint, string $key)
->setEndpoint($endpoint)
->setProject($project)
->setKey($key);
+
+ $this->functions = new Functions($this->client);
+ $this->storage = new Storage($this->client);
+ $this->teams = new Teams($this->client);
+ $this->users = new Users($this->client);
}
public static function getName(): string
@@ -60,6 +96,9 @@ public static function getName(): string
return 'Appwrite';
}
+ /**
+ * @return array
+ */
public static function getSupportedResources(): array
{
return [
@@ -83,160 +122,120 @@ public static function getSupportedResources(): array
Resource::TYPE_FUNCTION,
Resource::TYPE_DEPLOYMENT,
Resource::TYPE_ENVIRONMENT_VARIABLE,
-
- // Settings
];
}
+ /**
+ * @param array $resources
+ * @return array
+ * @throws AppwriteException
+ */
+ #[Override]
public function report(array $resources = []): array
{
if (empty($resources)) {
$resources = $this->getSupportedResources();
}
- $databases = new Databases($this->client);
- $functions = new Functions($this->client);
- $storage = new Storage($this->client);
- $teams = new Teams($this->client);
- $users = new Users($this->client);
+ $scope = '';
- $currentPermission = '';
// Most of these API calls are purposely wrong. Appwrite will throw a 403 before a 400.
// We want to make sure the API key has full read and write access to the project.
-
try {
// Auth
- if (in_array(Resource::TYPE_USER, $resources)) {
- $currentPermission = 'users.read';
- $users->list();
-
- $currentPermission = 'users.write';
- $users->create('', '', '');
- }
-
- if (in_array(Resource::TYPE_TEAM, $resources)) {
- $currentPermission = 'teams.read';
- $teams->list();
-
- $currentPermission = 'teams.write';
- $teams->create('', '');
- }
-
- if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) {
- $currentPermission = 'memberships.read';
- $teams->listMemberships('');
-
- $currentPermission = 'memberships.write';
- $teams->createMembership('', [], '');
- }
-
- // Database
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
- $currentPermission = 'database.read';
- $databases->list();
-
- $currentPermission = 'database.write';
- $databases->create('', '');
- }
-
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
- $currentPermission = 'collections.read';
- $databases->listCollections('');
-
- $currentPermission = 'collections.write';
- $databases->createCollection('', '', '');
- }
+ if (\in_array(Resource::TYPE_USER, $resources)) {
+ $scope = 'users.read';
+ $this->users->list();
- if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
- $currentPermission = 'attributes.read';
- $databases->listAttributes('', '');
-
- $currentPermission = 'attributes.write';
- $databases->createStringAttribute('', '', '', 0, false);
+ $scope = 'users.write';
+ $this->users->create('');
}
- if (in_array(Resource::TYPE_INDEX, $resources)) {
- $currentPermission = 'indexes.read';
- $databases->listIndexes('', '');
+ if (\in_array(Resource::TYPE_TEAM, $resources)) {
+ $scope = 'teams.read';
+ $this->teams->list();
- $currentPermission = 'indexes.write';
- $databases->createIndex('', '', '', '', []);
+ $scope = 'teams.write';
+ $this->teams->create('', '');
}
- if (in_array(Resource::TYPE_DOCUMENT, $resources)) {
- $currentPermission = 'documents.read';
- $databases->listDocuments('', '');
+ if (\in_array(Resource::TYPE_MEMBERSHIP, $resources)) {
+ $scope = 'memberships.read';
+ $this->teams->listMemberships('');
- $currentPermission = 'documents.write';
- $databases->createDocument('', '', '', []);
+ $scope = 'memberships.write';
+ $this->teams->createMembership('', [], '');
}
// Storage
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
- $currentPermission = 'storage.read';
- $storage->listBuckets();
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
+ $scope = 'storage.read';
+ $this->storage->listBuckets();
- $currentPermission = 'storage.write';
- $storage->createBucket('', '');
+ $scope = 'storage.write';
+ $this->storage->createBucket('', '');
}
- if (in_array(Resource::TYPE_FILE, $resources)) {
- $currentPermission = 'files.read';
- $storage->listFiles('');
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
+ $scope = 'files.read';
+ $this->storage->listFiles('');
- $currentPermission = 'files.write';
- $storage->createFile('', '', new InputFile());
+ $scope = 'files.write';
+ $this->storage->createFile('', '', new InputFile());
}
// Functions
- if (in_array(Resource::TYPE_FUNCTION, $resources)) {
- $currentPermission = 'functions.read';
- $functions->list();
+ if (\in_array(Resource::TYPE_FUNCTION, $resources)) {
+ $scope = 'functions.read';
+ $this->functions->list();
- $currentPermission = 'functions.write';
- $functions->create('', '', '');
+ $scope = 'functions.write';
+ $this->functions->create('', '', Runtime::NODE180());
}
- return [];
- } catch (\Throwable $exception) {
- if ($exception->getCode() === 403) {
- throw new \Exception('Missing permission: '.$currentPermission);
- } else {
- throw $exception;
+ } catch (AppwriteException $e) {
+ if ($e->getCode() === 403) {
+ throw new \Exception('Missing scope: ' . $scope, previous: $e);
}
+ throw $e;
}
+
+ return [];
}
+ /**
+ * @param array $resources
+ * @param callable $callback
+ * @return void
+ */
+ #[Override]
protected function import(array $resources, callable $callback): void
{
if (empty($resources)) {
return;
}
- foreach ($resources as $resource) {
- /** @var resource $resource */
+ $total = \count($resources);
+
+ foreach ($resources as $index => $resource) {
$resource->setStatus(Resource::STATUS_PROCESSING);
+ $isLast = $index === $total - 1;
+
try {
- switch ($resource->getGroup()) {
- case Transfer::GROUP_DATABASES:
- $responseResource = $this->importDatabaseResource($resource);
- break;
- case Transfer::GROUP_STORAGE:
- $responseResource = $this->importFileResource($resource);
- break;
- case Transfer::GROUP_AUTH:
- $responseResource = $this->importAuthResource($resource);
- break;
- case Transfer::GROUP_FUNCTIONS:
- $responseResource = $this->importFunctionResource($resource);
- break;
- }
+ $responseResource = match ($resource->getGroup()) {
+ Transfer::GROUP_DATABASES => $this->importDatabaseResource($resource, $isLast),
+ Transfer::GROUP_STORAGE => $this->importFileResource($resource),
+ Transfer::GROUP_AUTH => $this->importAuthResource($resource),
+ Transfer::GROUP_FUNCTIONS => $this->importFunctionResource($resource),
+ default => throw new \Exception('Invalid resource group'),
+ };
} catch (\Throwable $e) {
if ($e->getCode() === 409) {
$resource->setStatus(Resource::STATUS_SKIPPED, $e->getMessage());
} else {
$resource->setStatus(Resource::STATUS_ERROR, $e->getMessage());
+
$this->addError(new Exception(
resourceName: $resource->getName(),
resourceGroup: $resource->getGroup(),
@@ -256,191 +255,686 @@ protected function import(array $resources, callable $callback): void
$callback($resources);
}
- public function importDatabaseResource(Resource $resource): Resource
+ /**
+ * @throws AppwriteException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ public function importDatabaseResource(Resource $resource, bool $isLast): Resource
{
- $databaseService = new Databases($this->client);
-
switch ($resource->getName()) {
case Resource::TYPE_DATABASE:
/** @var Database $resource */
- $databaseService->create($resource->getId(), $resource->getDBName());
+ $success = $this->createDatabase($resource);
break;
case Resource::TYPE_COLLECTION:
/** @var Collection $resource */
- $newCollection = $databaseService->createCollection(
- $resource->getDatabase()->getId(),
- $resource->getId(),
- $resource->getCollectionName(),
- $resource->getPermissions(),
- $resource->getDocumentSecurity()
- );
- $resource->setId($newCollection['$id']);
- break;
- case Resource::TYPE_INDEX:
- /** @var Index $resource */
- $databaseService->createIndex(
- $resource->getCollection()->getDatabase()->getId(),
- $resource->getCollection()->getId(),
- $resource->getKey(),
- $resource->getType(),
- $resource->getAttributes(),
- $resource->getOrders()
- );
+ $success = $this->createCollection($resource);
break;
case Resource::TYPE_ATTRIBUTE:
/** @var Attribute $resource */
- $this->createAttribute($resource);
+ $success = $this->createAttribute($resource);
+ break;
+ case Resource::TYPE_INDEX:
+ /** @var Index $resource */
+ $success = $this->createIndex($resource);
break;
case Resource::TYPE_DOCUMENT:
/** @var Document $resource */
- // Check if document has already been created by subcollection
- $docExists = array_key_exists($resource->getId(), $this->cache->get(Resource::TYPE_DOCUMENT));
+ $success = $this->createDocument($resource, $isLast);
+ break;
+ default:
+ $success = false;
+ break;
+ }
- if ($docExists) {
- $resource->setStatus(Resource::STATUS_SKIPPED, 'Document has been already created by relationship');
+ if ($success) {
+ $resource->setStatus(Resource::STATUS_SUCCESS);
+ }
- return $resource;
- }
+ return $resource;
+ }
- $databaseService->createDocument(
- $resource->getDatabase()->getId(),
- $resource->getCollection()->getId(),
- $resource->getId(),
- $resource->getData(),
- $resource->getPermissions()
- );
- break;
+ /**
+ * @throws AuthorizationException
+ * @throws StructureException
+ * @throws DatabaseException
+ */
+ protected function createDatabase(Database $resource): bool
+ {
+ $resourceId = $resource->getId() == 'unique()'
+ ? ID::unique()
+ : $resource->getId();
+
+ $resource->setId($resourceId);
+
+ $database = $this->database->createDocument('databases', new UtopiaDocument([
+ '$id' => $resource->getId(),
+ 'name' => $resource->getDatabaseName(),
+ 'enabled' => true,
+ 'search' => implode(' ', [$resource->getId(), $resource->getDatabaseName()]),
+ ]));
+
+ $resource->setInternalId($database->getInternalId());
+
+ $attributes = \array_map(
+ fn ($attr) => new UtopiaDocument($attr),
+ $this->collectionStructure['attributes']
+ );
+ $indexes = \array_map(
+ fn ($index) => new UtopiaDocument($index),
+ $this->collectionStructure['indexes']
+ );
+
+ $this->database->createCollection(
+ 'database_' . $database->getInternalId(),
+ $attributes,
+ $indexes
+ );
+
+ return true;
+ }
+
+ /**
+ * @throws AuthorizationException
+ * @throws DatabaseException
+ * @throws StructureException
+ * @throws Exception
+ */
+ protected function createCollection(Collection $resource): bool
+ {
+ $resourceId = $resource->getId() == 'unique()'
+ ? ID::unique()
+ : $resource->getId();
+
+ $resource->setId($resourceId);
+
+ $database = $this->database->getDocument(
+ 'databases',
+ $resource->getDatabase()->getId()
+ );
+
+ if ($database->isEmpty()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Database not found',
+ );
}
- $resource->setStatus(Resource::STATUS_SUCCESS);
+ $collection = $this->database->createDocument('database_' . $database->getInternalId(), new UtopiaDocument([
+ '$id' => $resource->getId(),
+ 'databaseInternalId' => $database->getInternalId(),
+ 'databaseId' => $resource->getDatabase()->getId(),
+ '$permissions' => Permission::aggregate($resource->getPermissions()),
+ 'documentSecurity' => $resource->getDocumentSecurity(),
+ 'enabled' => true,
+ 'name' => $resource->getCollectionName(),
+ 'search' => implode(' ', [$resource->getId(), $resource->getCollectionName()]),
+ ]));
+
+ $resource->setInternalId($collection->getInternalId());
+
+ $this->database->createCollection(
+ 'database_' . $database->getInternalId() . '_collection_' . $resource->getInternalId(),
+ permissions: $resource->getPermissions(),
+ documentSecurity: $resource->getDocumentSecurity()
+ );
- return $resource;
+ return true;
}
- public function createAttribute(Attribute $attribute): void
+ /**
+ * @throws AppwriteException
+ * @throws \Exception
+ * @throws \Throwable
+ */
+ protected function createAttribute(Attribute $resource): bool
{
- $databaseService = new Databases($this->client);
+ $type = match ($resource->getType()) {
+ Attribute::TYPE_DATETIME => UtopiaDatabase::VAR_DATETIME,
+ Attribute::TYPE_BOOLEAN => UtopiaDatabase::VAR_BOOLEAN,
+ Attribute::TYPE_INTEGER => UtopiaDatabase::VAR_INTEGER,
+ Attribute::TYPE_FLOAT => UtopiaDatabase::VAR_FLOAT,
+ Attribute::TYPE_RELATIONSHIP => UtopiaDatabase::VAR_RELATIONSHIP,
+ Attribute::TYPE_STRING,
+ Attribute::TYPE_IP,
+ Attribute::TYPE_EMAIL,
+ Attribute::TYPE_URL,
+ Attribute::TYPE_ENUM => UtopiaDatabase::VAR_STRING,
+ default => throw new \Exception('Invalid resource type '.$resource->getType()),
+ };
+
+ $database = $this->database->getDocument(
+ 'databases',
+ $resource->getCollection()->getDatabase()->getId(),
+ );
+ if ($database->isEmpty()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Database not found',
+ );
+ }
- switch ($attribute->getTypeName()) {
- case Attribute::TYPE_STRING:
- /** @var Text $attribute */
- $databaseService->createStringAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getSize(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_INTEGER:
- /** @var int $attribute */
- $databaseService->createIntegerAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getMin(), $attribute->getMax() ?? null, $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_FLOAT:
- /** @var Decimal $attribute */
- $databaseService->createFloatAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), null, null, $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_BOOLEAN:
- /** @var bool $attribute */
- $databaseService->createBooleanAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_DATETIME:
- /** @var DateTime $attribute */
- $databaseService->createDatetimeAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_EMAIL:
- /** @var Email $attribute */
- $databaseService->createEmailAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_IP:
- /** @var IP $attribute */
- $databaseService->createIpAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_URL:
- /** @var URLAttribute $attribute */
- $databaseService->createUrlAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_ENUM:
- /** @var Enum $attribute */
- $databaseService->createEnumAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey(), $attribute->getElements(), $attribute->getRequired(), $attribute->getDefault(), $attribute->getArray());
- break;
- case Attribute::TYPE_RELATIONSHIP:
- /** @var Relationship $attribute */
- $databaseService->createRelationshipAttribute(
- $attribute->getCollection()->getDatabase()->getId(),
- $attribute->getCollection()->getId(),
- $attribute->getRelatedCollection(),
- $attribute->getRelationType(),
- $attribute->getTwoWay(),
- $attribute->getKey(),
- $attribute->getTwoWayKey(),
- $attribute->getOnDelete()
+ $collection = $this->database->getDocument(
+ 'database_' . $database->getInternalId(),
+ $resource->getCollection()->getId(),
+ );
+ if ($collection->isEmpty()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Collection not found',
+ );
+ }
+
+ if (!empty($resource->getFormat())) {
+ if (!Structure::hasFormat($resource->getFormat(), $type)) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: "Format {$resource->getFormat()} not available for attribute type {$type}",
);
- break;
- default:
- throw new \Exception('Invalid attribute type');
+ }
+ }
+ if ($resource->isRequired() && $resource->getDefault() !== null) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Cannot set default value for required attribute',
+ );
+ }
+ if ($resource->isArray() && $resource->getDefault() !== null) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Cannot set default value for array attribute',
+ );
+ }
+ if ($type === UtopiaDatabase::VAR_RELATIONSHIP) {
+ $resource->getOptions()['side'] = UtopiaDatabase::RELATION_SIDE_PARENT;
+ $relatedCollection = $this->database->getDocument(
+ 'database_' . $database->getInternalId(),
+ $resource->getOptions()['relatedCollection']
+ );
+ if ($relatedCollection->isEmpty()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Related collection not found',
+ );
+ }
}
- // Wait for attribute to be created
- $this->awaitAttributeCreation($attribute, 5);
+ try {
+ $attribute = new UtopiaDocument([
+ '$id' => ID::custom($database->getInternalId() . '_' . $collection->getInternalId() . '_' . $resource->getKey()),
+ 'key' => $resource->getKey(),
+ 'databaseInternalId' => $database->getInternalId(),
+ 'databaseId' => $database->getId(),
+ 'collectionInternalId' => $collection->getInternalId(),
+ 'collectionId' => $collection->getId(),
+ 'type' => $type,
+ 'status' => 'available',
+ 'size' => $resource->getSize(),
+ 'required' => $resource->isRequired(),
+ 'signed' => $resource->isSigned(),
+ 'default' => $resource->getDefault(),
+ 'array' => $resource->isArray(),
+ 'format' => $resource->getFormat(),
+ 'formatOptions' => $resource->getFormatOptions(),
+ 'filters' => $resource->getFilters(),
+ 'options' => $resource->getOptions(),
+ ]);
+
+ $this->database->checkAttribute($collection, $attribute);
+
+ $attribute = $this->database->createDocument('attributes', $attribute);
+ } catch (DuplicateException) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Attribute already exists',
+ );
+ } catch (LimitException) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Attribute limit exceeded',
+ );
+ } catch (\Throwable $e) {
+ $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
+ $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
+ throw $e;
+ }
+
+ $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
+ $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
+ $options = $resource->getOptions();
+
+ $twoWayKey = null;
+
+ if ($type === UtopiaDatabase::VAR_RELATIONSHIP && $options['twoWay']) {
+ $twoWayKey = $options['twoWayKey'];
+ $options['relatedCollection'] = $collection->getId();
+ $options['twoWayKey'] = $resource->getKey();
+ $options['side'] = UtopiaDatabase::RELATION_SIDE_CHILD;
+
+ try {
+ $twoWayAttribute = new UtopiaDocument([
+ '$id' => ID::custom($database->getInternalId() . '_' . $relatedCollection->getInternalId() . '_' . $twoWayKey),
+ 'key' => $twoWayKey,
+ 'databaseInternalId' => $database->getInternalId(),
+ 'databaseId' => $database->getId(),
+ 'collectionInternalId' => $relatedCollection->getInternalId(),
+ 'collectionId' => $relatedCollection->getId(),
+ 'type' => $type,
+ 'status' => 'available',
+ 'size' => $resource->getSize(),
+ 'required' => $resource->isRequired(),
+ 'signed' => $resource->isSigned(),
+ 'default' => $resource->getDefault(),
+ 'array' => $resource->isArray(),
+ 'format' => $resource->getFormat(),
+ 'formatOptions' => $resource->getFormatOptions(),
+ 'filters' => $resource->getFilters(),
+ 'options' => $options,
+ ]);
+
+ $this->database->createDocument('attributes', $twoWayAttribute);
+ } catch (DuplicateException) {
+ $this->database->deleteDocument('attributes', $attribute->getId());
+
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Attribute already exists',
+ );
+ } catch (LimitException) {
+ $this->database->deleteDocument('attributes', $attribute->getId());
+
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Attribute limit exceeded',
+ );
+ } catch (\Throwable $e) {
+ $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
+ $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId());
+ throw $e;
+ }
+ }
+
+ try {
+ switch ($type) {
+ case UtopiaDatabase::VAR_RELATIONSHIP:
+ if (!$this->database->createRelationship(
+ collection: 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
+ relatedCollection: 'database_' . $database->getInternalId() . '_collection_' . $relatedCollection->getInternalId(),
+ type: $options['relationType'],
+ twoWay: $options['twoWay'],
+ id: $resource->getKey(),
+ twoWayKey: $options['twoWay'] ? $twoWayKey : $options['twoWayKey'] ?? null,
+ onDelete: $options['onDelete'],
+ )) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Failed to create relationship',
+ );
+ }
+ break;
+ default:
+ if (!$this->database->createAttribute(
+ 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
+ $resource->getKey(),
+ $type,
+ $resource->getSize(),
+ $resource->isRequired(),
+ $resource->getDefault(),
+ $resource->isSigned(),
+ $resource->isArray(),
+ $resource->getFormat(),
+ $resource->getFormatOptions(),
+ $resource->getFilters(),
+ )) {
+ throw new \Exception('Failed to create Attribute');
+ }
+ }
+ } catch (\Throwable) {
+ $this->database->deleteDocument('attributes', $attribute->getId());
+
+ if (isset($twoWayAttribute)) {
+ $this->database->deleteDocument('attributes', $twoWayAttribute->getId());
+ }
+
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Failed to create attribute',
+ );
+ }
+
+ if ($type === UtopiaDatabase::VAR_RELATIONSHIP && $options['twoWay']) {
+ $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $relatedCollection->getId());
+ }
+
+ $this->database->purgeCachedDocument('database_' . $database->getInternalId(), $collection->getId());
+ $this->database->purgeCachedCollection('database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId());
+
+ return true;
}
/**
- * Await Attribute Creation
+ * @throws Exception
+ * @throws \Throwable
*/
- public function awaitAttributeCreation(Attribute $attribute, int $timeout): bool
+ protected function createIndex(Index $resource): bool
{
- $databaseService = new Databases($this->client);
+ $database = $this->database->getDocument(
+ 'databases',
+ $resource->getCollection()->getDatabase()->getId(),
+ );
+ if ($database->isEmpty()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Database not found',
+ );
+ }
+
+ $collection = $this->database->getDocument(
+ 'database_' . $database->getInternalId(),
+ $resource->getCollection()->getId(),
+ );
+ if ($collection->isEmpty()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Collection not found',
+ );
+ }
+
+ $count = $this->database->count('indexes', [
+ Query::equal('collectionInternalId', [$collection->getInternalId()]),
+ Query::equal('databaseInternalId', [$database->getInternalId()])
+ ], $this->database->getLimitForIndexes());
+
+ if ($count >= $this->database->getLimitForIndexes()) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Index limit reached for collection',
+ );
+ }
+
+ /**
+ * @var array $collectionAttributes
+ */
+ $collectionAttributes = $collection->getAttribute('attributes');
+
+ $oldAttributes = \array_map(
+ fn ($attr) => $attr->getArrayCopy(),
+ $collectionAttributes
+ );
+
+ $oldAttributes[] = [
+ 'key' => '$id',
+ 'type' => UtopiaDatabase::VAR_STRING,
+ 'status' => 'available',
+ 'required' => true,
+ 'array' => false,
+ 'default' => null,
+ 'size' => UtopiaDatabase::LENGTH_KEY
+ ];
+ $oldAttributes[] = [
+ 'key' => '$createdAt',
+ 'type' => UtopiaDatabase::VAR_DATETIME,
+ 'status' => 'available',
+ 'signed' => false,
+ 'required' => false,
+ 'array' => false,
+ 'default' => null,
+ 'size' => 0
+ ];
+ $oldAttributes[] = [
+ 'key' => '$updatedAt',
+ 'type' => UtopiaDatabase::VAR_DATETIME,
+ 'status' => 'available',
+ 'signed' => false,
+ 'required' => false,
+ 'array' => false,
+ 'default' => null,
+ 'size' => 0
+ ];
+
+ // Lengths hidden by default
+ $lengths = [];
- $start = \time();
+ foreach ($resource->getAttributes() as $i => $attribute) {
+ // find attribute metadata in collection document
+ $attributeIndex = \array_search(
+ $attribute,
+ \array_column($oldAttributes, 'key')
+ );
- while (\time() - $start < $timeout) {
- $response = $databaseService->getAttribute($attribute->getCollection()->getDatabase()->getId(), $attribute->getCollection()->getId(), $attribute->getKey());
+ if ($attributeIndex === false) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Attribute not found in collection: ' . $attribute,
+ );
+ }
- if ($response['status'] === 'available') {
- return true;
+ $attributeStatus = $oldAttributes[$attributeIndex]['status'];
+ $attributeType = $oldAttributes[$attributeIndex]['type'];
+ $attributeSize = $oldAttributes[$attributeIndex]['size'];
+ $attributeArray = $oldAttributes[$attributeIndex]['array'] ?? false;
+
+ if ($attributeType === UtopiaDatabase::VAR_RELATIONSHIP) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Relationship attributes are not supported in indexes',
+ );
}
- \usleep(500000);
+ // Ensure attribute is available
+ if ($attributeStatus !== 'available') {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Attribute not available: ' . $attribute,
+ );
+ }
+
+ $lengths[$i] = null;
+
+ if ($attributeType === UtopiaDatabase::VAR_STRING) {
+ $lengths[$i] = $attributeSize; // set attribute size as index length only for strings
+ }
+
+ if ($attributeArray === true) {
+ $lengths[$i] = UtopiaDatabase::ARRAY_INDEX_LENGTH;
+ $orders[$i] = null;
+ }
}
- throw new \Exception('Attribute creation timeout');
+ $index = new UtopiaDocument([
+ '$id' => ID::custom($database->getInternalId() . '_' . $collection->getInternalId() . '_' . $resource->getKey()),
+ 'key' => $resource->getKey(),
+ 'status' => 'available', // processing, available, failed, deleting, stuck
+ 'databaseInternalId' => $database->getInternalId(),
+ 'databaseId' => $database->getId(),
+ 'collectionInternalId' => $collection->getInternalId(),
+ 'collectionId' => $collection->getId(),
+ 'type' => $resource->getType(),
+ 'attributes' => $resource->getAttributes(),
+ 'lengths' => $lengths,
+ 'orders' => $resource->getOrders(),
+ ]);
+
+ $validator = new IndexValidator(
+ $collectionAttributes,
+ $this->database->getAdapter()->getMaxIndexLength()
+ );
+
+ if (!$validator->isValid($index)) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Invalid index: ' . $validator->getDescription(),
+ );
+ }
+
+ $index = $this->database->createDocument('indexes', $index);
+
+ try {
+ $result = $this->database->createIndex(
+ 'database_' . $database->getInternalId() . '_collection_' . $collection->getInternalId(),
+ $resource->getKey(),
+ $resource->getType(),
+ $resource->getAttributes(),
+ $lengths,
+ $resource->getOrders()
+ );
+
+ if (!$result) {
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Failed to create index',
+ );
+ }
+ } catch (\Throwable $th) {
+ $this->database->deleteDocument('indexes', $index->getId());
+
+ throw new Exception(
+ resourceName: $resource->getName(),
+ resourceGroup: $resource->getGroup(),
+ resourceId: $resource->getId(),
+ message: 'Failed to create index',
+ );
+ }
+
+ $this->database->purgeCachedDocument(
+ 'database_' . $database->getInternalId(),
+ $collection->getId()
+ );
+
+ return true;
}
- public function importFileResource(File|Bucket $resource): Resource
+ /**
+ * @throws AuthorizationException
+ * @throws DatabaseException
+ * @throws StructureException
+ */
+ protected function createDocument(Document $resource, bool $isLast): bool
{
- $storageService = new Storage($this->client);
+ // Check if document has already been created
+ $exists = \array_key_exists(
+ $resource->getId(),
+ $this->cache->get(Resource::TYPE_DOCUMENT)
+ );
- $response = null;
+ if ($exists) {
+ $resource->setStatus(
+ Resource::STATUS_SKIPPED,
+ 'Document has already been created'
+ );
+ return false;
+ }
+ $this->documentBuffer[] = new UtopiaDocument(\array_merge([
+ '$id' => $resource->getId(),
+ '$permissions' => $resource->getPermissions(),
+ ], $resource->getData()));
+
+ if ($isLast) {
+ try {
+ $database = $this->database->getDocument(
+ 'databases',
+ $resource->getCollection()->getDatabase()->getId(),
+ );
+ $collection = $this->database->getDocument(
+ 'database_' . $database->getInternalId(),
+ $resource->getCollection()->getId(),
+ );
+
+ $databaseInternalId = $database->getInternalId();
+ $collectionInternalId = $collection->getInternalId();
+
+ $this->database
+ ->setPreserveDates(true)
+ ->createDocuments(
+ 'database_' . $databaseInternalId . '_collection_' . $collectionInternalId,
+ $this->documentBuffer
+ );
+ } finally {
+ $this->documentBuffer = [];
+ $this->database->setPreserveDates(false);
+ }
+ }
+
+
+ return true;
+ }
+
+ /**
+ * @throws AppwriteException
+ */
+ public function importFileResource(Resource $resource): Resource
+ {
switch ($resource->getName()) {
case Resource::TYPE_FILE:
/** @var File $resource */
return $this->importFile($resource);
- break;
case Resource::TYPE_BUCKET:
/** @var Bucket $resource */
- if (! $resource->getUpdateLimits()) {
- $response = $storageService->createBucket(
- $resource->getId() ?? 'unique()',
- $resource->getBucketName(),
- $resource->getPermissions(),
- $resource->getFileSecurity(),
- true, // Set to true for now, we'll come back later.
- null,
- null,
- $resource->getCompression() ?? 'none',
- $resource->getEncryption() ?? null,
- $resource->getAntiVirus() ?? null
- );
- } else {
- $response = $storageService->updateBucket(
- $resource->getId(),
- $resource->getBucketName(),
- $resource->getPermissions(),
- $resource->getFileSecurity(),
- $resource->getEnabled(),
- $resource->getMaxFileSize() ?? null,
- $resource->getAllowedFileExtensions() ?? null,
- $resource->getCompression() ?? 'none',
- $resource->getEncryption() ?? null,
- $resource->getAntiVirus() ?? null
- );
- }
+
+ $compression = match ($resource->getCompression()) {
+ 'none' => Compression::NONE(),
+ 'gzip' => Compression::GZIP(),
+ 'zstd' => Compression::ZSTD(),
+ default => throw new \Exception('Invalid Compression: ' . $resource->getCompression()),
+ };
+
+ $response = $this->storage->createBucket(
+ $resource->getId(),
+ $resource->getBucketName(),
+ $resource->getPermissions(),
+ $resource->getFileSecurity(),
+ $resource->getEnabled(),
+ $resource->getMaxFileSize(),
+ $resource->getAllowedFileExtensions(),
+ $compression,
+ $resource->getEncryption(),
+ $resource->getAntiVirus()
+ );
+
$resource->setId($response['$id']);
}
@@ -453,6 +947,7 @@ public function importFileResource(File|Bucket $resource): Resource
* Import File Data
*
* @returns File
+ * @throws AppwriteException
*/
public function importFile(File $file): File
{
@@ -472,7 +967,7 @@ public function importFile(File $file): File
[
'bucketId' => $bucketId,
'fileId' => $file->getId(),
- 'file' => new \CurlFile('data://'.$file->getMimeType().';base64,'.base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()),
+ 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()),
'permissions' => $file->getPermissions(),
]
);
@@ -488,14 +983,14 @@ public function importFile(File $file): File
"/storage/buckets/{$bucketId}/files",
[
'content-type' => 'multipart/form-data',
- 'content-range' => 'bytes '.($file->getStart()).'-'.($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()).'/'.$file->getSize(),
- 'X-Appwrite-Project' => $this->project,
- 'x-Appwrite-Key' => $this->key,
+ 'content-range' => 'bytes ' . ($file->getStart()) . '-' . ($file->getEnd() == ($file->getSize() - 1) ? $file->getSize() : $file->getEnd()) . '/' . $file->getSize(),
+ 'x-appwrite-project' => $this->project,
+ 'x-appwrite-key' => $this->key,
],
[
'bucketId' => $bucketId,
'fileId' => $file->getId(),
- 'file' => new \CurlFile('data://'.$file->getMimeType().';base64,'.base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()),
+ 'file' => new \CurlFile('data://' . $file->getMimeType() . ';base64,' . base64_encode($file->getData()), $file->getMimeType(), $file->getFileName()),
'permissions' => $file->getPermissions(),
]
);
@@ -503,9 +998,9 @@ public function importFile(File $file): File
if ($file->getEnd() == ($file->getSize() - 1)) {
$file->setStatus(Resource::STATUS_SUCCESS);
- // Signatures for Encrypted files are invalid, so we skip the check
- if ($file->getBucket()->getEncryption() == false || $file->getSize() > (20 * 1024 * 1024)) {
- if ($response['signature'] !== $file->getSignature()) {
+ // Signatures for encrypted files are invalid, so we skip the check
+ if (!$file->getBucket()->getEncryption() || $file->getSize() > (20 * 1024 * 1024)) {
+ if (\is_array($response) && $response['signature'] !== $file->getSignature()) {
$file->setStatus(Resource::STATUS_WARNING, 'File signature mismatch, Possibly corrupted.');
}
}
@@ -516,18 +1011,18 @@ public function importFile(File $file): File
return $file;
}
+ /**
+ * @throws AppwriteException
+ */
public function importAuthResource(Resource $resource): Resource
{
- $userService = new Users($this->client);
- $teamService = new Teams($this->client);
-
switch ($resource->getName()) {
case Resource::TYPE_USER:
/** @var User $resource */
- if (! empty($resource->getPasswordHash())) {
+ if (!empty($resource->getPasswordHash())) {
$this->importPasswordUser($resource);
} else {
- $userService->create(
+ $this->users->create(
$resource->getId(),
$resource->getEmail(),
$resource->getPhone(),
@@ -536,44 +1031,48 @@ public function importAuthResource(Resource $resource): Resource
);
}
- if ($resource->getUsername()) {
- $userService->updateName($resource->getId(), $resource->getUsername());
+ if (!empty($resource->getUsername())) {
+ $this->users->updateName($resource->getId(), $resource->getUsername());
}
- if ($resource->getPhone()) {
- $userService->updatePhone($resource->getId(), $resource->getPhone());
+ if (!empty($resource->getPhone())) {
+ $this->users->updatePhone($resource->getId(), $resource->getPhone());
}
if ($resource->getEmailVerified()) {
- $userService->updateEmailVerification($resource->getId(), $resource->getEmailVerified());
+ $this->users->updateEmailVerification($resource->getId(), true);
}
if ($resource->getPhoneVerified()) {
- $userService->updatePhoneVerification($resource->getId(), $resource->getPhoneVerified());
+ $this->users->updatePhoneVerification($resource->getId(), true);
}
if ($resource->getDisabled()) {
- $userService->updateStatus($resource->getId(), ! $resource->getDisabled());
+ $this->users->updateStatus($resource->getId(), false);
}
- if ($resource->getPreferences()) {
- $userService->updatePrefs($resource->getId(), $resource->getPreferences());
+ if (!empty($resource->getPreferences())) {
+ $this->users->updatePrefs($resource->getId(), $resource->getPreferences());
}
- if ($resource->getLabels()) {
- $userService->updateLabels($resource->getId(), $resource->getLabels());
+ if (!empty($resource->getLabels())) {
+ $this->users->updateLabels($resource->getId(), $resource->getLabels());
}
break;
case Resource::TYPE_TEAM:
/** @var Team $resource */
- $teamService->create($resource->getId(), $resource->getTeamName());
- $teamService->updatePrefs($resource->getId(), $resource->getPreferences());
+ $this->teams->create($resource->getId(), $resource->getTeamName());
+
+ if (!empty($resource->getPreferences())) {
+ $this->teams->updatePrefs($resource->getId(), $resource->getPreferences());
+ }
break;
case Resource::TYPE_MEMBERSHIP:
/** @var Membership $resource */
$user = $resource->getUser();
- $teamService->createMembership(
+
+ $this->teams->createMembership(
$resource->getTeam()->getId(),
$resource->getRoles(),
userId: $user->getId(),
@@ -586,19 +1085,24 @@ public function importAuthResource(Resource $resource): Resource
return $resource;
}
+ /**
+ * @param User $user
+ * @return array|null
+ * @throws AppwriteException
+ * @throws \Exception
+ */
public function importPasswordUser(User $user): ?array
{
- $auth = new Users($this->client);
$hash = $user->getPasswordHash();
$result = null;
- if (! $hash) {
+ if (!$hash) {
throw new \Exception('Password hash is missing');
}
switch ($hash->getAlgorithm()) {
case Hash::ALGORITHM_SCRYPT_MODIFIED:
- $result = $auth->createScryptModifiedUser(
+ $result = $this->users->createScryptModifiedUser(
$user->getId(),
$user->getEmail(),
$hash->getHash(),
@@ -609,7 +1113,7 @@ public function importPasswordUser(User $user): ?array
);
break;
case Hash::ALGORITHM_BCRYPT:
- $result = $auth->createBcryptUser(
+ $result = $this->users->createBcryptUser(
$user->getId(),
$user->getEmail(),
$hash->getHash(),
@@ -617,7 +1121,7 @@ public function importPasswordUser(User $user): ?array
);
break;
case Hash::ALGORITHM_ARGON2:
- $result = $auth->createArgon2User(
+ $result = $this->users->createArgon2User(
$user->getId(),
$user->getEmail(),
$hash->getHash(),
@@ -625,16 +1129,16 @@ public function importPasswordUser(User $user): ?array
);
break;
case Hash::ALGORITHM_SHA256:
- $result = $auth->createShaUser(
+ $result = $this->users->createShaUser(
$user->getId(),
$user->getEmail(),
$hash->getHash(),
- 'sha256',
+ PasswordHash::SHA256(),
empty($user->getUsername()) ? null : $user->getUsername()
);
break;
case Hash::ALGORITHM_PHPASS:
- $result = $auth->createPHPassUser(
+ $result = $this->users->createPHPassUser(
$user->getId(),
$user->getEmail(),
$hash->getHash(),
@@ -642,7 +1146,7 @@ public function importPasswordUser(User $user): ?array
);
break;
case Hash::ALGORITHM_SCRYPT:
- $result = $auth->createScryptUser(
+ $result = $this->users->createScryptUser(
$user->getId(),
$user->getEmail(),
$hash->getHash(),
@@ -655,7 +1159,7 @@ public function importPasswordUser(User $user): ?array
);
break;
case Hash::ALGORITHM_PLAINTEXT:
- $result = $auth->create(
+ $result = $this->users->create(
$user->getId(),
$user->getEmail(),
$user->getPhone(),
@@ -668,35 +1172,90 @@ public function importPasswordUser(User $user): ?array
return $result;
}
+ /**
+ * @throws AppwriteException
+ */
public function importFunctionResource(Resource $resource): Resource
{
- $functions = new Functions($this->client);
-
switch ($resource->getName()) {
case Resource::TYPE_FUNCTION:
/** @var Func $resource */
- $functions->create(
+
+ $runtime = match ($resource->getRuntime()) {
+ 'node-14.5' => Runtime::NODE145(),
+ 'node-16.0' => Runtime::NODE160(),
+ 'node-18.0' => Runtime::NODE180(),
+ 'node-19.0' => Runtime::NODE190(),
+ 'node-20.0' => Runtime::NODE200(),
+ 'node-21.0' => Runtime::NODE210(),
+ 'php-8.0' => Runtime::PHP80(),
+ 'php-8.1' => Runtime::PHP81(),
+ 'php-8.2' => Runtime::PHP82(),
+ 'php-8.3' => Runtime::PHP83(),
+ 'ruby-3.0' => Runtime::RUBY30(),
+ 'ruby-3.1' => Runtime::RUBY31(),
+ 'ruby-3.2' => Runtime::RUBY32(),
+ 'ruby-3.3' => Runtime::RUBY33(),
+ 'python-3.8' => Runtime::PYTHON38(),
+ 'python-3.9' => Runtime::PYTHON39(),
+ 'python-3.10' => Runtime::PYTHON310(),
+ 'python-3.11' => Runtime::PYTHON311(),
+ 'python-3.12' => Runtime::PYTHON312(),
+ 'python-ml-3.11' => Runtime::PYTHONML311(),
+ 'dart-3.0' => Runtime::DART30(),
+ 'dart-3.1' => Runtime::DART31(),
+ 'dart-3.3' => Runtime::DART33(),
+ 'dart-2.15' => Runtime::DART215(),
+ 'dart-2.16' => Runtime::DART216(),
+ 'dart-2.17' => Runtime::DART217(),
+ 'dart-2.18' => Runtime::DART218(),
+ 'deno-1.21' => Runtime::DENO121(),
+ 'deno-1.24' => Runtime::DENO124(),
+ 'deno-1.35' => Runtime::DENO135(),
+ 'deno-1.40' => Runtime::DENO140(),
+ 'dotnet-3.1' => Runtime::DOTNET31(),
+ 'dotnet-6.0' => Runtime::DOTNET60(),
+ 'dotnet-7.0' => Runtime::DOTNET70(),
+ 'java-8.0' => Runtime::JAVA80(),
+ 'java-11.0' => Runtime::JAVA110(),
+ 'java-17.0' => Runtime::JAVA170(),
+ 'java-18.0' => Runtime::JAVA180(),
+ 'java-21.0' => Runtime::JAVA210(),
+ 'swift-5.5' => Runtime::SWIFT55(),
+ 'swift-5.8' => Runtime::SWIFT58(),
+ 'swift-5.9' => Runtime::SWIFT59(),
+ 'kotlin-1.6' => Runtime::KOTLIN16(),
+ 'kotlin-1.8' => Runtime::KOTLIN18(),
+ 'kotlin-1.9' => Runtime::KOTLIN19(),
+ 'cpp-17' => Runtime::CPP17(),
+ 'cpp-20' => Runtime::CPP20(),
+ 'bun-1.0' => Runtime::BUN10(),
+ default => throw new \Exception('Invalid Runtime: ' . $resource->getRuntime()),
+ };
+
+ $this->functions->create(
$resource->getId(),
$resource->getFunctionName(),
- $resource->getRuntime(),
+ $runtime,
$resource->getExecute(),
$resource->getEvents(),
$resource->getSchedule(),
$resource->getTimeout(),
- $resource->getEnabled()
+ $resource->getEnabled(),
+ entrypoint: $resource->getEntrypoint(),
);
break;
case Resource::TYPE_ENVIRONMENT_VARIABLE:
/** @var EnvVar $resource */
- $functions->createVariable(
+ $this->functions->createVariable(
$resource->getFunc()->getId(),
$resource->getKey(),
$resource->getValue()
);
break;
case Resource::TYPE_DEPLOYMENT:
+ /** @var Deployment $resource */
return $this->importDeployment($resource);
- break;
}
$resource->setStatus(Resource::STATUS_SUCCESS);
@@ -704,6 +1263,10 @@ public function importFunctionResource(Resource $resource): Resource
return $resource;
}
+ /**
+ * @throws AppwriteException
+ * @throws \Exception
+ */
private function importDeployment(Deployment $deployment): Resource
{
$functionId = $deployment->getFunction()->getId();
@@ -719,7 +1282,7 @@ private function importDeployment(Deployment $deployment): Resource
],
[
'functionId' => $functionId,
- 'code' => new \CurlFile('data://application/gzip;base64,'.base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'),
+ 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'),
'activate' => $deployment->getActivated() ? 'true' : 'false',
'entrypoint' => $deployment->getEntrypoint(),
]
@@ -735,17 +1298,21 @@ private function importDeployment(Deployment $deployment): Resource
"/v1/functions/{$functionId}/deployments",
[
'content-type' => 'multipart/form-data',
- 'content-range' => 'bytes '.($deployment->getStart()).'-'.($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()).'/'.$deployment->getSize(),
+ 'content-range' => 'bytes ' . ($deployment->getStart()) . '-' . ($deployment->getEnd() == ($deployment->getSize() - 1) ? $deployment->getSize() : $deployment->getEnd()) . '/' . $deployment->getSize(),
'x-appwrite-id' => $deployment->getId(),
],
[
'functionId' => $functionId,
- 'code' => new \CurlFile('data://application/gzip;base64,'.base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'),
+ 'code' => new \CurlFile('data://application/gzip;base64,' . base64_encode($deployment->getData()), 'application/gzip', 'deployment.tar.gz'),
'activate' => $deployment->getActivated(),
'entrypoint' => $deployment->getEntrypoint(),
]
);
+ if (!\is_array($response) || !isset($response['$id'])) {
+ throw new \Exception('Deployment creation failed');
+ }
+
if ($deployment->getStart() === 0) {
$deployment->setId($response['$id']);
}
diff --git a/src/Migration/Destinations/Local.php b/src/Migration/Destinations/Local.php
index ad3e22a..68ea59d 100644
--- a/src/Migration/Destinations/Local.php
+++ b/src/Migration/Destinations/Local.php
@@ -6,7 +6,6 @@
use Utopia\Migration\Resource;
use Utopia\Migration\Resources\Functions\Deployment;
use Utopia\Migration\Resources\Storage\File;
-use Utopia\Migration\Transfer;
/**
* Local
@@ -16,6 +15,9 @@
*/
class Local extends Destination
{
+ /**
+ * @var array>>
+ */
private array $data = [];
protected string $path;
@@ -26,8 +28,8 @@ public function __construct(string $path)
if (! \file_exists($this->path)) {
mkdir($this->path, 0777, true);
- mkdir($this->path.'/files', 0777, true);
- mkdir($this->path.'/deployments', 0777, true);
+ mkdir($this->path . '/files', 0777, true);
+ mkdir($this->path . '/deployments', 0777, true);
}
}
@@ -36,72 +38,84 @@ public static function getName(): string
return 'Local';
}
+ /**
+ * @return array
+ */
public static function getSupportedResources(): array
{
return [
- Resource::TYPE_ATTRIBUTE,
- Resource::TYPE_BUCKET,
- Resource::TYPE_COLLECTION,
+ // Auth
+ Resource::TYPE_USER,
+ Resource::TYPE_TEAM,
+ Resource::TYPE_MEMBERSHIP,
+ Resource::TYPE_HASH,
+
+ // Database
Resource::TYPE_DATABASE,
- Resource::TYPE_DEPLOYMENT,
+ Resource::TYPE_COLLECTION,
+ Resource::TYPE_ATTRIBUTE,
+ Resource::TYPE_INDEX,
Resource::TYPE_DOCUMENT,
- Resource::TYPE_ENVIRONMENT_VARIABLE,
+
+ // Storage
+ Resource::TYPE_BUCKET,
Resource::TYPE_FILE,
+
+ // Functions
Resource::TYPE_FUNCTION,
- Resource::TYPE_HASH,
- Resource::TYPE_INDEX,
- Resource::TYPE_TEAM,
- Resource::TYPE_MEMBERSHIP,
- Resource::TYPE_USER,
+ Resource::TYPE_DEPLOYMENT,
+ Resource::TYPE_ENVIRONMENT_VARIABLE,
];
}
+ /**
+ * @throws \Exception
+ */
public function report(array $resources = []): array
{
$report = [];
- if (empty($resources)) {
- $resources = $this->getSupportedResources();
- }
-
- // Check we can write to the file
- if (! \is_writable($this->path.'/backup.json')) {
- $report[Transfer::GROUP_DATABASES][] = 'Unable to write to file: '.$this->path;
- throw new \Exception('Unable to write to file: '.$this->path);
+ if (!\is_writable($this->path . '/backup.json')) {
+ throw new \Exception('Unable to write to file: ' . $this->path);
}
return $report;
}
+ /**
+ * @throws \Exception
+ */
private function sync(): void
{
- $jsonEncodedData = \json_encode($this->data, JSON_PRETTY_PRINT);
+ $json = \json_encode($this->data, JSON_PRETTY_PRINT);
- if ($jsonEncodedData === false) {
+ if ($json === false) {
throw new \Exception('Unable to encode data to JSON, Are you accidentally encoding binary data?');
}
- \file_put_contents($this->path.'/backup.json', \json_encode($this->data, JSON_PRETTY_PRINT));
+ \file_put_contents($this->path . '/backup.json', $json);
}
+ /**
+ * @param array $resources
+ * @param callable $callback
+ * @throws \Exception
+ */
protected function import(array $resources, callable $callback): void
{
foreach ($resources as $resource) {
- /** @var resource $resource */
switch ($resource->getName()) {
case Resource::TYPE_DEPLOYMENT:
/** @var Deployment $resource */
if ($resource->getStart() === 0) {
- $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray();
+ $this->data[$resource->getGroup()][$resource->getName()][] = (string) \json_encode($resource);
}
- file_put_contents($this->path.'deployments/'.$resource->getId().'.tar.gz', $resource->getData(), FILE_APPEND);
+ file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND);
$resource->setData('');
break;
case Resource::TYPE_FILE:
/** @var File $resource */
-
- // Handle folders
if (str_contains($resource->getFileName(), '/')) {
$folders = explode('/', $resource->getFileName());
$folderPath = $this->path.'/files';
@@ -123,12 +137,13 @@ protected function import(array $resources, callable $callback): void
$resource->setData('');
break;
default:
- $this->data[$resource->getGroup()][$resource->getName()][] = $resource->asArray();
+ $this->data[$resource->getGroup()][$resource->getName()][] = (string) \json_encode($resource);
break;
}
$resource->setStatus(Resource::STATUS_SUCCESS);
$this->cache->update($resource);
+
$this->sync();
}
diff --git a/src/Migration/Exception.php b/src/Migration/Exception.php
index 46ecf52..1b7ed0f 100644
--- a/src/Migration/Exception.php
+++ b/src/Migration/Exception.php
@@ -2,16 +2,22 @@
namespace Utopia\Migration;
-class Exception extends \Exception
+class Exception extends \Exception implements \JsonSerializable
{
public string $resourceName;
public string $resourceGroup;
- public string $resourceId;
+ public ?string $resourceId;
- public function __construct(string $resourceName, string $resourceGroup, string $message, int $code = 0, ?\Throwable $previous = null, string $resourceId = '')
- {
+ public function __construct(
+ string $resourceName,
+ string $resourceGroup,
+ ?string $resourceId = null,
+ string $message = '',
+ int $code = 0,
+ ?\Throwable $previous = null,
+ ) {
$this->resourceName = $resourceName;
$this->resourceId = $resourceId;
$this->resourceGroup = $resourceGroup;
@@ -31,6 +37,21 @@ public function getResourceGroup(): string
public function getResourceId(): string
{
- return $this->resourceId;
+ return $this->resourceId ?? '';
+ }
+
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'code' => $this->getCode(),
+ 'message' => $this->getMessage(),
+ 'resourceName' => $this->resourceName,
+ 'resourceGroup' => $this->resourceGroup,
+ 'resourceId' => $this->resourceId,
+ 'trace' => $this->getPrevious()?->getTraceAsString(),
+ ];
}
}
diff --git a/src/Migration/Resource.php b/src/Migration/Resource.php
index 0516dd9..d317b76 100644
--- a/src/Migration/Resource.php
+++ b/src/Migration/Resource.php
@@ -2,7 +2,7 @@
namespace Utopia\Migration;
-abstract class Resource
+abstract class Resource implements \JsonSerializable
{
public const STATUS_PENDING = 'pending';
@@ -52,7 +52,7 @@ abstract class Resource
public const TYPE_HASH = 'hash';
- public const TYPE_ENVIRONMENT_VARIABLE = 'environment variable';
+ public const TYPE_ENVIRONMENT_VARIABLE = 'environment-variable';
public const ALL_RESOURCES = [
self::TYPE_ATTRIBUTE,
@@ -81,6 +81,9 @@ abstract class Resource
protected string $message = '';
+ /**
+ * @var array
+ */
protected array $permissions = [];
abstract public static function getName(): string;
@@ -149,7 +152,7 @@ public function setMessage(string $message): self
}
/**
- * @returns string[]
+ * @returns array
*/
public function getPermissions(): array
{
@@ -157,7 +160,7 @@ public function getPermissions(): array
}
/**
- * @param string[] $permissions
+ * @param array $permissions
*/
public function setPermissions(array $permissions): self
{
@@ -165,6 +168,4 @@ public function setPermissions(array $permissions): self
return $this;
}
-
- abstract public function asArray(): array;
}
diff --git a/src/Migration/Resources/Auth/Hash.php b/src/Migration/Resources/Auth/Hash.php
index e1fef05..401cd66 100644
--- a/src/Migration/Resources/Auth/Hash.php
+++ b/src/Migration/Resources/Auth/Hash.php
@@ -10,51 +10,70 @@
*/
class Hash extends Resource
{
- public const ALGORITHM_SCRYPT_MODIFIED = 'scryptModified';
+ public const string ALGORITHM_SCRYPT_MODIFIED = 'scryptModified';
- public const ALGORITHM_BCRYPT = 'bcrypt';
+ public const string ALGORITHM_BCRYPT = 'bcrypt';
- public const ALGORITHM_MD5 = 'md5';
+ public const string ALGORITHM_MD5 = 'md5';
- public const ALGORITHM_ARGON2 = 'argon2';
+ public const string ALGORITHM_ARGON2 = 'argon2';
- public const ALGORITHM_SHA256 = 'sha256';
+ public const string ALGORITHM_SHA256 = 'sha256';
- public const ALGORITHM_PHPASS = 'phpass';
+ public const string ALGORITHM_PHPASS = 'phpass';
- public const ALGORITHM_SCRYPT = 'scrypt';
+ public const string ALGORITHM_SCRYPT = 'scrypt';
- public const ALGORITHM_PLAINTEXT = 'plainText';
+ public const string ALGORITHM_PLAINTEXT = 'plainText';
- private string $hash;
-
- private string $salt = '';
-
- private string $algorithm = self::ALGORITHM_SHA256;
-
- private string $separator = '';
-
- private string $signingKey = '';
-
- private int $passwordCpu = 0;
-
- private int $passwordMemory = 0;
-
- private int $passwordParallel = 0;
+ public function __construct(
+ private readonly string $hash,
+ private readonly string $salt = '',
+ private readonly string $algorithm = self::ALGORITHM_SHA256,
+ private readonly string $separator = '',
+ private readonly string $signingKey = '',
+ private readonly int $passwordCpu = 0,
+ private readonly int $passwordMemory = 0,
+ private readonly int $passwordParallel = 0,
+ private readonly int $passwordLength = 0
+ ) {
+ }
- private int $passwordLength = 0;
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['hash'] ?? '',
+ $array['salt'] ?? '',
+ $array['algorithm'] ?? self::ALGORITHM_SHA256,
+ $array['separator'] ?? '',
+ $array['signingKey'] ?? '',
+ $array['passwordCpu'] ?? 0,
+ $array['passwordMemory'] ?? 0,
+ $array['passwordParallel'] ?? 0,
+ $array['passwordLength'] ?? 0
+ );
+ }
- public function __construct(string $hash, string $salt = '', string $algorithm = self::ALGORITHM_SHA256, string $separator = '', string $signingKey = '', int $passwordCpu = 0, int $passwordMemory = 0, int $passwordParallel = 0, int $passwordLength = 0)
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->hash = $hash;
- $this->salt = $salt;
- $this->algorithm = $algorithm;
- $this->separator = $separator;
- $this->signingKey = $signingKey;
- $this->passwordCpu = $passwordCpu;
- $this->passwordMemory = $passwordMemory;
- $this->passwordParallel = $passwordParallel;
- $this->passwordLength = $passwordLength;
+ return [
+ 'hash' => $this->hash,
+ 'salt' => $this->salt,
+ 'algorithm' => $this->algorithm,
+ 'separator' => $this->separator,
+ 'signingKey' => $this->signingKey,
+ 'passwordCpu' => $this->passwordCpu,
+ 'passwordMemory' => $this->passwordMemory,
+ 'passwordParallel' => $this->passwordParallel,
+ 'passwordLength' => $this->passwordLength,
+ ];
}
public static function getName(): string
@@ -75,16 +94,6 @@ public function getHash(): string
return $this->hash;
}
- /**
- * Set Hash
- */
- public function setHash(string $hash): self
- {
- $this->hash = $hash;
-
- return $this;
- }
-
/**
* Get Salt
*/
@@ -93,16 +102,6 @@ public function getSalt(): string
return $this->salt;
}
- /**
- * Set Salt
- */
- public function setSalt(string $salt): self
- {
- $this->salt = $salt;
-
- return $this;
- }
-
/**
* Get Algorithm
*/
@@ -111,16 +110,6 @@ public function getAlgorithm(): string
return $this->algorithm;
}
- /**
- * Set Algorithm
- */
- public function setAlgorithm(string $algorithm): self
- {
- $this->algorithm = $algorithm;
-
- return $this;
- }
-
/**
* Get Separator
*/
@@ -129,16 +118,6 @@ public function getSeparator(): string
return $this->separator;
}
- /**
- * Set Separator
- */
- public function setSeparator(string $separator): self
- {
- $this->separator = $separator;
-
- return $this;
- }
-
/**
* Get Signing Key
*/
@@ -147,16 +126,6 @@ public function getSigningKey(): string
return $this->signingKey;
}
- /**
- * Set Signing Key
- */
- public function setSigningKey(string $signingKey): self
- {
- $this->signingKey = $signingKey;
-
- return $this;
- }
-
/**
* Get Password CPU
*/
@@ -165,16 +134,6 @@ public function getPasswordCpu(): int
return $this->passwordCpu;
}
- /**
- * Set Password CPU
- */
- public function setPasswordCpu(int $passwordCpu): self
- {
- $this->passwordCpu = $passwordCpu;
-
- return $this;
- }
-
/**
* Get Password Memory
*/
@@ -183,16 +142,6 @@ public function getPasswordMemory(): int
return $this->passwordMemory;
}
- /**
- * Set Password Memory
- */
- public function setPasswordMemory(int $passwordMemory): self
- {
- $this->passwordMemory = $passwordMemory;
-
- return $this;
- }
-
/**
* Get Password Parallel
*/
@@ -201,16 +150,6 @@ public function getPasswordParallel(): int
return $this->passwordParallel;
}
- /**
- * Set Password Parallel
- */
- public function setPasswordParallel(int $passwordParallel): self
- {
- $this->passwordParallel = $passwordParallel;
-
- return $this;
- }
-
/**
* Get Password Length
*/
@@ -218,32 +157,4 @@ public function getPasswordLength(): int
{
return $this->passwordLength;
}
-
- /**
- * Set Password Length
- */
- public function setPasswordLength(int $passwordLength): self
- {
- $this->passwordLength = $passwordLength;
-
- return $this;
- }
-
- /**
- * As Array
- */
- public function asArray(): array
- {
- return [
- 'hash' => $this->hash,
- 'salt' => $this->salt,
- 'algorithm' => $this->algorithm,
- 'separator' => $this->separator,
- 'signingKey' => $this->signingKey,
- 'passwordCpu' => $this->passwordCpu,
- 'passwordMemory' => $this->passwordMemory,
- 'passwordParallel' => $this->passwordParallel,
- 'passwordLength' => $this->passwordLength,
- ];
- }
}
diff --git a/src/Migration/Resources/Auth/Membership.php b/src/Migration/Resources/Auth/Membership.php
index efe5816..a97915a 100644
--- a/src/Migration/Resources/Auth/Membership.php
+++ b/src/Migration/Resources/Auth/Membership.php
@@ -10,20 +10,50 @@
*/
class Membership extends Resource
{
- protected Team $team;
-
- protected User $user;
-
- protected array $roles;
+ /**
+ * @param string $id
+ * @param Team $team
+ * @param User $user
+ * @param array $roles
+ * @param bool $active
+ */
+ public function __construct(
+ string $id,
+ private readonly Team $team,
+ private readonly User $user,
+ private readonly array $roles = [],
+ private readonly bool $active = true
+ ) {
+ $this->id = $id;
+ }
- protected bool $active = true;
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ Team::fromArray($array['team'] ?? []),
+ User::fromArray($array['user'] ?? []),
+ $array['roles'] ?? [],
+ $array['active'] ?? true
+ );
+ }
- public function __construct(Team $team, User $user, array $roles = [], bool $active = true)
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->team = $team;
- $this->user = $user;
- $this->roles = $roles;
- $this->active = $active;
+ return [
+ 'id' => $this->id,
+ 'team' => $this->team,
+ 'user' => $this->user,
+ 'roles' => $this->roles,
+ 'active' => $this->active,
+ ];
}
public static function getName(): string
@@ -41,55 +71,21 @@ public function getTeam(): Team
return $this->team;
}
- public function setTeam(Team $team): self
- {
- $this->team = $team;
-
- return $this;
- }
-
public function getUser(): User
{
return $this->user;
}
- public function setUser(User $user): self
- {
- $this->user = $user;
-
- return $this;
- }
-
+ /**
+ * @return array
+ */
public function getRoles(): array
{
return $this->roles;
}
- public function setRoles(array $roles): self
- {
- $this->roles = $roles;
-
- return $this;
- }
-
public function getActive(): bool
{
return $this->active;
}
-
- public function setActive(bool $active): self
- {
- $this->active = $active;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'userId' => $this->user->getId(),
- 'roles' => $this->roles,
- 'active' => $this->active,
- ];
- }
}
diff --git a/src/Migration/Resources/Auth/Team.php b/src/Migration/Resources/Auth/Team.php
index b379129..c9d8440 100644
--- a/src/Migration/Resources/Auth/Team.php
+++ b/src/Migration/Resources/Auth/Team.php
@@ -3,23 +3,50 @@
namespace Utopia\Migration\Resources\Auth;
use Utopia\Migration\Resource;
-use Utopia\Migration\Resources\User;
use Utopia\Migration\Transfer;
class Team extends Resource
{
- protected string $name;
-
- protected array $preferences = [];
+ /**
+ * @param string $id
+ * @param string $name
+ * @param array $preferences
+ * @param array $members
+ */
+ public function __construct(
+ string $id,
+ private readonly string $name,
+ private readonly array $preferences = [],
+ private readonly array $members = []
+ ) {
+ $this->id = $id;
+ }
- protected array $members = [];
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ $array['name'],
+ $array['preferences'] ?? [],
+ $array['members'] ?? []
+ );
+ }
- public function __construct(string $id, string $name, array $preferences = [], array $members = [])
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->id = $id;
- $this->name = $name;
- $this->preferences = $preferences;
- $this->members = $members;
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'preferences' => $this->preferences,
+ 'members' => $this->members,
+ ];
}
public static function getName(): string
@@ -37,46 +64,19 @@ public function getTeamName(): string
return $this->name;
}
- public function setTeamName(string $name): self
- {
- $this->name = $name;
-
- return $this;
- }
-
+ /**
+ * @return array
+ */
public function getPreferences(): array
{
return $this->preferences;
}
- public function setPreferences(array $preferences): self
- {
- $this->preferences = $preferences;
-
- return $this;
- }
-
- public function getMembers(): array
- {
- return $this->members;
- }
-
/**
- * @param User[] $members
+ * @return array
*/
- public function setMembers(array $members): self
- {
- $this->members = $members;
-
- return $this;
- }
-
- public function asArray(): array
+ public function getMembers(): array
{
- return [
- 'id' => $this->id,
- 'name' => $this->name,
- 'preferences' => $this->preferences,
- ];
+ return $this->members;
}
}
diff --git a/src/Migration/Resources/Auth/User.php b/src/Migration/Resources/Auth/User.php
index df8430c..3f72eef 100644
--- a/src/Migration/Resources/Auth/User.php
+++ b/src/Migration/Resources/Auth/User.php
@@ -7,94 +7,97 @@
class User extends Resource
{
- protected ?string $email = null;
-
- protected ?string $username = null;
-
- protected ?Hash $passwordHash = null;
-
- protected ?string $phone = null;
-
- protected array $labels = [];
-
- protected string $oauthProvider = '';
-
- protected bool $emailVerified = false;
-
- protected bool $phoneVerified = false;
-
- protected bool $disabled = false;
-
- protected array $preferences = [];
-
+ /**
+ * @param string $id
+ * @param string $email
+ * @param string $username
+ * @param ?Hash $passwordHash
+ * @param ?string $phone
+ * @param array $labels
+ * @param string $oauthProvider
+ * @param bool $emailVerified
+ * @param bool $phoneVerified
+ * @param bool $disabled
+ * @param array $preferences
+ */
public function __construct(
string $id,
- ?string $email = null,
- ?string $username = null,
- ?Hash $passwordHash = null,
- ?string $phone = null,
- array $labels = [],
- string $oauthProvider = '',
- bool $emailVerified = false,
- bool $phoneVerified = false,
- bool $disabled = false,
- array $preferences = []
+ private readonly string $email = '',
+ private readonly string $username = '',
+ private readonly ?Hash $passwordHash = null,
+ private readonly ?string $phone = null,
+ private readonly array $labels = [],
+ private readonly string $oauthProvider = '',
+ private readonly bool $emailVerified = false,
+ private readonly bool $phoneVerified = false,
+ private readonly bool $disabled = false,
+ private readonly array $preferences = []
) {
$this->id = $id;
- $this->email = $email;
- $this->username = $username;
- $this->passwordHash = $passwordHash;
- $this->phone = $phone;
- $this->labels = $labels;
- $this->oauthProvider = $oauthProvider;
- $this->emailVerified = $emailVerified;
- $this->phoneVerified = $phoneVerified;
- $this->disabled = $disabled;
- $this->preferences = $preferences;
}
/**
- * Get Name
+ * @param array $array
+ * @return self
*/
- public static function getName(): string
+ public static function fromArray(array $array): self
{
- return Resource::TYPE_USER;
+ return new self(
+ $array['id'],
+ $array['email'] ?? '',
+ $array['username'] ?? '',
+ $array['passwordHash'] ?? null,
+ $array['phone'] ?? '',
+ $array['labels'] ?? [],
+ $array['oauthProvider'] ?? '',
+ $array['emailVerified'] ?? false,
+ $array['phoneVerified'] ?? false,
+ $array['disabled'] ?? false,
+ $array['preferences'] ?? []
+ );
}
/**
- * Get Email
+ * @return array
*/
- public function getEmail(): ?string
+ public function jsonSerialize(): array
{
- return $this->email;
+ return [
+ 'id' => $this->id,
+ 'email' => $this->email,
+ 'username' => $this->username,
+ 'passwordHash' => $this->passwordHash,
+ 'phone' => $this->phone,
+ 'labels' => $this->labels,
+ 'oauthProvider' => $this->oauthProvider,
+ 'emailVerified' => $this->emailVerified,
+ 'phoneVerified' => $this->phoneVerified,
+ 'disabled' => $this->disabled,
+ 'preferences' => $this->preferences,
+ ];
}
/**
- * Set Email
+ * Get Name
*/
- public function setEmail(string $email): self
+ public static function getName(): string
{
- $this->email = $email;
-
- return $this;
+ return Resource::TYPE_USER;
}
/**
- * Get Username
+ * Get Email
*/
- public function getUsername(): ?string
+ public function getEmail(): string
{
- return $this->username;
+ return $this->email;
}
-
/**
- * Set Username
+ * Get Username
*/
- public function setUsername(string $username): self
+ public function getUsername(): ?string
{
- $this->username = $username;
-
- return $this;
+ return $this->username;
}
/**
@@ -105,16 +108,6 @@ public function getPasswordHash(): ?Hash
return $this->passwordHash;
}
- /**
- * Set Password Hash
- */
- public function setPasswordHash(Hash $passwordHash): self
- {
- $this->passwordHash = $passwordHash;
-
- return $this;
- }
-
/**
* Get Phone
*/
@@ -123,34 +116,16 @@ public function getPhone(): ?string
return $this->phone;
}
- /**
- * Set Phone
- */
- public function setPhone(string $phone): self
- {
- $this->phone = $phone;
-
- return $this;
- }
-
/**
* Get Labels
+ *
+ * @return array
*/
public function getLabels(): array
{
return $this->labels;
}
- /**
- * Set Labels
- */
- public function setLabels(array $labels): self
- {
- $this->labels = $labels;
-
- return $this;
- }
-
/**
* Get OAuth Provider
*/
@@ -159,16 +134,6 @@ public function getOAuthProvider(): string
return $this->oauthProvider;
}
- /**
- * Set OAuth Provider
- */
- public function setOAuthProvider(string $oauthProvider): self
- {
- $this->oauthProvider = $oauthProvider;
-
- return $this;
- }
-
/**
* Get Email Verified
*/
@@ -177,16 +142,6 @@ public function getEmailVerified(): bool
return $this->emailVerified;
}
- /**
- * Set Email Verified
- */
- public function setEmailVerified(bool $verified): self
- {
- $this->emailVerified = $verified;
-
- return $this;
- }
-
/**
* Get Email Verified
*/
@@ -195,16 +150,6 @@ public function getPhoneVerified(): bool
return $this->phoneVerified;
}
- /**
- * Set Phone Verified
- */
- public function setPhoneVerified(bool $verified): self
- {
- $this->phoneVerified = $verified;
-
- return $this;
- }
-
public function getGroup(): string
{
return Transfer::GROUP_AUTH;
@@ -218,48 +163,13 @@ public function getDisabled(): bool
return $this->disabled;
}
- /**
- * Set Disabled
- */
- public function setDisabled(bool $disabled): self
- {
- $this->disabled = $disabled;
-
- return $this;
- }
-
/**
* Get Preferences
+ *
+ * @return array
*/
public function getPreferences(): array
{
return $this->preferences;
}
-
- /**
- * Set Preferences
- */
- public function setPreferences(array $preferences): self
- {
- $this->preferences = $preferences;
-
- return $this;
- }
-
- /**
- * As Array
- */
- public function asArray(): array
- {
- return [
- 'id' => $this->id,
- 'email' => $this->email,
- 'username' => $this->username,
- 'passwordHash' => $this->passwordHash ? $this->passwordHash->asArray() : null,
- 'phone' => $this->phone,
- 'oauthProvider' => $this->oauthProvider,
- 'emailVerified' => $this->emailVerified,
- 'phoneVerified' => $this->phoneVerified,
- ];
- }
}
diff --git a/src/Migration/Resources/Database/Attribute.php b/src/Migration/Resources/Database/Attribute.php
index a81909b..2d50d2a 100644
--- a/src/Migration/Resources/Database/Attribute.php
+++ b/src/Migration/Resources/Database/Attribute.php
@@ -7,43 +7,64 @@
abstract class Attribute extends Resource
{
- public const TYPE_STRING = 'string';
+ public const string TYPE_STRING = 'string';
+ public const string TYPE_INTEGER = 'int';
+ public const string TYPE_FLOAT = 'float';
+ public const string TYPE_BOOLEAN = 'bool';
+ public const string TYPE_DATETIME = 'dateTime';
+ public const string TYPE_EMAIL = 'email';
+ public const string TYPE_ENUM = 'enum';
+ public const string TYPE_IP = 'IP';
+ public const string TYPE_URL = 'URL';
+ public const string TYPE_RELATIONSHIP = 'relationship';
- public const TYPE_INTEGER = 'int';
-
- public const TYPE_FLOAT = 'float';
-
- public const TYPE_BOOLEAN = 'bool';
-
- public const TYPE_DATETIME = 'dateTime';
-
- public const TYPE_EMAIL = 'email';
-
- public const TYPE_ENUM = 'enum';
-
- public const TYPE_IP = 'IP';
-
- public const TYPE_URL = 'URL';
-
- public const TYPE_RELATIONSHIP = 'relationship';
-
- protected string $key;
-
- protected bool $required;
-
- protected bool $array;
-
- protected Collection $collection;
+ /**
+ * @param string $key
+ * @param Collection $collection
+ * @param int $size
+ * @param bool $required
+ * @param mixed|null $default
+ * @param bool $array
+ * @param bool $signed
+ * @param string $format
+ * @param array $formatOptions
+ * @param array $filters
+ * @param array $options
+ */
+ public function __construct(
+ protected readonly string $key,
+ protected readonly Collection $collection,
+ protected readonly int $size = 0,
+ protected readonly bool $required = false,
+ protected readonly mixed $default = null,
+ protected readonly bool $array = false,
+ protected readonly bool $signed = false,
+ protected readonly string $format = '',
+ protected readonly array $formatOptions = [],
+ protected readonly array $filters = [],
+ protected array $options = [],
+ ) {
+ }
/**
- * @param int $size
+ * @return array
*/
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false)
+ public function jsonSerialize(): array
{
- $this->key = $key;
- $this->required = $required;
- $this->array = $array;
- $this->collection = $collection;
+ return [
+ 'key' => $this->key,
+ 'collection' => $this->collection,
+ 'type' => $this->getType(),
+ 'size' => $this->size,
+ 'required' => $this->required,
+ 'default' => $this->default,
+ 'array' => $this->array,
+ 'signed' => $this->signed,
+ 'format' => $this->format,
+ 'formatOptions' => $this->formatOptions,
+ 'filters' => $this->filters,
+ 'options' => $this->options,
+ ];
}
public static function getName(): string
@@ -51,7 +72,7 @@ public static function getName(): string
return Resource::TYPE_ATTRIBUTE;
}
- abstract public function getTypeName(): string;
+ abstract public function getType(): string;
public function getGroup(): string
{
@@ -63,56 +84,62 @@ public function getKey(): string
return $this->key;
}
- public function setKey(string $key): self
- {
- $this->key = $key;
-
- return $this;
- }
-
public function getCollection(): Collection
{
return $this->collection;
}
- public function setCollection(Collection $collection)
+ public function getSize(): int
{
- $this->collection = $collection;
-
- return $this;
+ return $this->size;
}
- public function getRequired(): bool
+ public function isRequired(): bool
{
return $this->required;
}
- public function setRequired(bool $required): self
+ public function getDefault(): mixed
{
- $this->required = $required;
-
- return $this;
+ return $this->default;
}
- public function getArray(): bool
+ public function isArray(): bool
{
return $this->array;
}
- public function setArray(bool $array): self
+ public function isSigned(): bool
{
- $this->array = $array;
+ return $this->signed;
+ }
- return $this;
+ public function getFormat(): string
+ {
+ return $this->format;
}
- public function asArray(): array
+ /**
+ * @return array
+ */
+ public function getFormatOptions(): array
{
- return [
- 'key' => $this->key,
- 'required' => $this->required,
- 'array' => $this->array,
- 'type' => $this->getName(),
- ];
+ return $this->formatOptions;
+ }
+
+ /**
+ * @return array
+ */
+ public function getFilters(): array
+ {
+ return $this->filters;
+ }
+
+ /**
+ * @return array
+ */
+ public function &getOptions(): array
+ {
+ return $this->options;
}
}
diff --git a/src/Migration/Resources/Database/Attributes/Boolean.php b/src/Migration/Resources/Database/Attributes/Boolean.php
index 1d43f67..73a2927 100644
--- a/src/Migration/Resources/Database/Attributes/Boolean.php
+++ b/src/Migration/Resources/Database/Attributes/Boolean.php
@@ -7,42 +7,54 @@
class Boolean extends Attribute
{
- protected string $key;
-
- protected bool $required;
-
- protected bool $array;
-
- protected ?bool $default;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?bool $default = null,
+ bool $array = false,
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ required: $required,
+ default: $default,
+ array: $array
+ );
+ }
/**
- * @param ?bool $default
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * required: bool,
+ * array: bool,
+ * default: ?bool,
+ * } $array
+ * @return self
*/
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?bool $default = null)
+ public static function fromArray(array $array): self
{
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ required: $array['required'],
+ default: $array['default'],
+ array: $array['array'],
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_BOOLEAN;
}
-
- public function getDefault(): ?bool
- {
- return $this->default;
- }
-
- public function setDefault(bool $default): void
- {
- $this->default = $default;
- }
-
- public function asArray(): array
- {
- return array_merge(parent::asArray(), [
- 'default' => $this->default,
- ]);
- }
}
diff --git a/src/Migration/Resources/Database/Attributes/DateTime.php b/src/Migration/Resources/Database/Attributes/DateTime.php
index 363f0f5..5ad52c6 100644
--- a/src/Migration/Resources/Database/Attributes/DateTime.php
+++ b/src/Migration/Resources/Database/Attributes/DateTime.php
@@ -7,29 +7,55 @@
class DateTime extends Attribute
{
- protected ?string $default;
-
- /**
- * @param ?string $default
- */
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null)
- {
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?string $default = null,
+ bool $array = false,
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ required: $required,
+ default: $default,
+ array: $array,
+ filters: ['datetime'],
+ );
}
- public function getDefault(): ?string
+ public function getType(): string
{
- return $this->default;
- }
-
- public function setDefault(string $default): void
- {
- $this->default = $default;
+ return Attribute::TYPE_DATETIME;
}
- public function getTypeName(): string
+ /**
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * required: bool,
+ * array: bool,
+ * default: ?string,
+ * } $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
{
- return Attribute::TYPE_DATETIME;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ required: $array['required'],
+ default: $array['default'],
+ array: $array['array'],
+ );
}
}
diff --git a/src/Migration/Resources/Database/Attributes/Decimal.php b/src/Migration/Resources/Database/Attributes/Decimal.php
index 111b326..2109855 100644
--- a/src/Migration/Resources/Database/Attributes/Decimal.php
+++ b/src/Migration/Resources/Database/Attributes/Decimal.php
@@ -7,72 +7,81 @@
class Decimal extends Attribute
{
- protected ?float $default;
-
- protected ?float $min;
-
- protected ?float $max;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?float $default = null,
+ bool $array = false,
+ ?float $min = null,
+ ?float $max = null,
+ bool $signed = true,
+ ) {
+ $min ??= PHP_FLOAT_MIN;
+ $max ??= PHP_FLOAT_MAX;
+
+ parent::__construct(
+ $key,
+ $collection,
+ required: $required,
+ default: $default,
+ array: $array,
+ signed: $signed,
+ formatOptions: [
+ 'min' => $min,
+ 'max' => $max,
+ ]
+ );
+ }
/**
- * @param ?float $default
- * @param ?float $min
- * @param ?float $max
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * required: bool,
+ * array: bool,
+ * default: ?float,
+ * formatOptions: array{
+ * min: ?float,
+ * max: ?float
+ * }
+ * } $array
+ * @return self
*/
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?float $default = null, ?float $min = null, ?float $max = null)
+ public static function fromArray(array $array): self
{
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- $this->min = $min;
- $this->max = $max;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ required: $array['required'],
+ default: $array['default'],
+ array: $array['array'],
+ min: $array['formatOptions']['min'],
+ max: $array['formatOptions']['max'],
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_FLOAT;
}
public function getMin(): ?float
{
- return $this->min;
+ return (float)$this->formatOptions['min'];
}
public function getMax(): ?float
{
- return $this->max;
- }
-
- public function setMin(float $min): self
- {
- $this->min = $min;
-
- return $this;
- }
-
- public function setMax(float $max): self
- {
- $this->max = $max;
-
- return $this;
- }
-
- public function getDefault(): ?float
- {
- return $this->default;
- }
-
- public function setDefault(float $default): self
- {
- $this->default = $default;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return array_merge(parent::asArray(), [
- 'min' => $this->getMin(),
- 'max' => $this->getMax(),
- 'default' => $this->getDefault(),
- ]);
+ return (float)$this->formatOptions['max'];
}
}
diff --git a/src/Migration/Resources/Database/Attributes/Email.php b/src/Migration/Resources/Database/Attributes/Email.php
index 4fa025a..57dabc4 100644
--- a/src/Migration/Resources/Database/Attributes/Email.php
+++ b/src/Migration/Resources/Database/Attributes/Email.php
@@ -5,30 +5,28 @@
use Utopia\Migration\Resources\Database\Attribute;
use Utopia\Migration\Resources\Database\Collection;
-class Email extends Attribute
+class Email extends Text
{
- protected ?string $default;
-
- /**
- * @param ?string $default
- */
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null)
- {
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- }
-
- public function getDefault(): ?string
- {
- return $this->default;
- }
-
- public function setDefault(string $default): void
- {
- $this->default = $default;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?string $default = null,
+ bool $array = false,
+ int $size = 254
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ required: $required,
+ default: $default,
+ array: $array,
+ size: $size,
+ format: 'email',
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_EMAIL;
}
diff --git a/src/Migration/Resources/Database/Attributes/Enum.php b/src/Migration/Resources/Database/Attributes/Enum.php
index e80d36d..0c24e27 100644
--- a/src/Migration/Resources/Database/Attributes/Enum.php
+++ b/src/Migration/Resources/Database/Attributes/Enum.php
@@ -7,52 +7,78 @@
class Enum extends Attribute
{
- protected ?string $default;
-
- protected array $elements;
+ /**
+ * @param array $elements
+ */
+ public function __construct(
+ string $key,
+ Collection $collection,
+ array $elements,
+ bool $required = false,
+ ?string $default = null,
+ bool $array = false,
+ int $size = 256
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ size: $size,
+ required: $required,
+ default: $default,
+ array: $array,
+ format: 'enum',
+ formatOptions: [
+ 'elements' => $elements,
+ ],
+ );
+ }
/**
- * @param string[] $elements
- * @param ?string $default
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * size: int,
+ * required: bool,
+ * default: ?string,
+ * array: bool,
+ * formatOptions: array{
+ * elements: array
+ * }
+ * } $array
+ * @return self
*/
- public function __construct(string $key, Collection $collection, array $elements, bool $required, bool $array, ?string $default)
+ public static function fromArray(array $array): self
{
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- $this->elements = $elements;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ elements: $array['formatOptions']['elements'],
+ required: $array['required'],
+ default: $array['default'],
+ array: $array['array'],
+ size: $array['size'],
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_ENUM;
}
+ /**
+ * @return array
+ */
public function getElements(): array
{
- return $this->elements;
- }
-
- public function setElements(array $elements): self
- {
- $this->elements = $elements;
-
- return $this;
- }
-
- public function getDefault(): ?string
- {
- return $this->default;
- }
-
- public function setDefault(string $default): void
- {
- $this->default = $default;
- }
-
- public function asArray(): array
- {
- return array_merge(parent::asArray(), [
- 'elements' => $this->elements,
- ]);
+ return (array)$this->formatOptions['elements'];
}
}
diff --git a/src/Migration/Resources/Database/Attributes/IP.php b/src/Migration/Resources/Database/Attributes/IP.php
index 35f3c1d..f843995 100644
--- a/src/Migration/Resources/Database/Attributes/IP.php
+++ b/src/Migration/Resources/Database/Attributes/IP.php
@@ -5,27 +5,28 @@
use Utopia\Migration\Resources\Database\Attribute;
use Utopia\Migration\Resources\Database\Collection;
-class IP extends Attribute
+class IP extends Text
{
- protected ?string $default;
-
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null)
- {
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- }
-
- public function getDefault(): ?string
- {
- return $this->default;
- }
-
- public function setDefault(string $default): void
- {
- $this->default = $default;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?string $default = null,
+ bool $array = false,
+ int $size = 39
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ required: $required,
+ default: $default,
+ array: $array,
+ size: $size,
+ format: 'ip',
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_IP;
}
diff --git a/src/Migration/Resources/Database/Attributes/Integer.php b/src/Migration/Resources/Database/Attributes/Integer.php
index 75ecee1..0f107df 100644
--- a/src/Migration/Resources/Database/Attributes/Integer.php
+++ b/src/Migration/Resources/Database/Attributes/Integer.php
@@ -7,72 +7,83 @@
class Integer extends Attribute
{
- protected ?int $default;
-
- protected ?int $min;
-
- protected ?int $max;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?int $default = null,
+ bool $array = false,
+ ?int $min = null,
+ ?int $max = null,
+ bool $signed = true,
+ ) {
+ $min ??= PHP_INT_MIN;
+ $max ??= PHP_INT_MAX;
+ $size = $max > 2147483647 ? 8 : 4;
+
+ parent::__construct(
+ $key,
+ $collection,
+ size: $size,
+ required: $required,
+ default: $default,
+ array: $array,
+ signed: $signed,
+ formatOptions: [
+ 'min' => $min,
+ 'max' => $max,
+ ]
+ );
+ }
/**
- * @param ?int $default
- * @param ?int $min
- * @param ?int $max
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * required: bool,
+ * array: bool,
+ * default: ?int,
+ * formatOptions: array{
+ * min: ?int,
+ * max: ?int
+ * }
+ * } $array
+ * @return self
*/
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?int $default = null, ?int $min = null, ?int $max = null)
+ public static function fromArray(array $array): self
{
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- $this->min = $min;
- $this->max = $max;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ required: $array['required'],
+ default: $array['default'],
+ array: $array['array'],
+ min: $array['formatOptions']['min'] ?? null,
+ max: $array['formatOptions']['max'] ?? null
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_INTEGER;
}
public function getMin(): ?int
{
- return $this->min;
+ return (int)$this->formatOptions['min'];
}
public function getMax(): ?int
{
- return $this->max;
- }
-
- public function setMin(?int $min): self
- {
- $this->min = $min;
-
- return $this;
- }
-
- public function setMax(?int $max): self
- {
- $this->max = $max;
-
- return $this;
- }
-
- public function getDefault(): ?int
- {
- return $this->default;
- }
-
- public function setDefault(int $default): self
- {
- $this->default = $default;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return array_merge(parent::asArray(), [
- 'min' => $this->getMin(),
- 'max' => $this->getMax(),
- 'default' => $this->getDefault(),
- ]);
+ return (int)$this->formatOptions['max'];
}
}
diff --git a/src/Migration/Resources/Database/Attributes/Relationship.php b/src/Migration/Resources/Database/Attributes/Relationship.php
index 0aa3b48..b332d65 100644
--- a/src/Migration/Resources/Database/Attributes/Relationship.php
+++ b/src/Migration/Resources/Database/Attributes/Relationship.php
@@ -2,96 +2,106 @@
namespace Utopia\Migration\Resources\Database\Attributes;
+use Utopia\Database\Database;
use Utopia\Migration\Resources\Database\Attribute;
use Utopia\Migration\Resources\Database\Collection;
class Relationship extends Attribute
{
- protected string $relatedCollection;
-
- protected string $relationType;
-
- protected bool $twoWay;
-
- protected string $twoWayKey;
-
- protected string $onDelete;
-
- protected string $side;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ string $relatedCollection,
+ string $relationType,
+ bool $twoWay = false,
+ ?string $twoWayKey = null,
+ string $onDelete = Database::RELATION_MUTATE_RESTRICT,
+ string $side = Database::RELATION_SIDE_PARENT
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ options: [
+ 'relatedCollection' => $relatedCollection,
+ 'relationType' => $relationType,
+ 'twoWay' => $twoWay,
+ 'twoWayKey' => $twoWayKey,
+ 'onDelete' => $onDelete,
+ 'side' => $side,
+ ]
+ );
+ }
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, string $relatedCollection = '', string $relationType = '', bool $twoWay = false, string $twoWayKey = '', string $onDelete = '', string $side = '')
+ /**
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * options: array{
+ * relatedCollection: string,
+ * relationType: string,
+ * twoWay: bool,
+ * twoWayKey: ?string,
+ * onDelete: string,
+ * side: string,
+ * }
+ * } $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
{
- parent::__construct($key, $collection, $required, $array);
- $this->relatedCollection = $relatedCollection;
- $this->relationType = $relationType;
- $this->twoWay = $twoWay;
- $this->twoWayKey = $twoWayKey;
- $this->onDelete = $onDelete;
- $this->side = $side;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ relatedCollection: $array['options']['relatedCollection'],
+ relationType: $array['options']['relationType'],
+ twoWay: $array['options']['twoWay'],
+ twoWayKey: $array['options']['twoWayKey'],
+ onDelete: $array['options']['onDelete'],
+ side: $array['options']['side'],
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_RELATIONSHIP;
}
public function getRelatedCollection(): string
{
- return $this->relatedCollection;
- }
-
- public function setRelatedCollection(string $relatedCollection): void
- {
- $this->relatedCollection = $relatedCollection;
+ return $this->options['relatedCollection'];
}
public function getRelationType(): string
{
- return $this->relationType;
- }
-
- public function setRelationType(string $relationType): void
- {
- $this->relationType = $relationType;
+ return $this->options['relationType'];
}
public function getTwoWay(): bool
{
- return $this->twoWay;
+ return $this->options['twoWay'];
}
- public function setTwoWay(bool $twoWay): void
+ public function getTwoWayKey(): ?string
{
- $this->twoWay = $twoWay;
- }
-
- public function getTwoWayKey(): string
- {
- return $this->twoWayKey;
- }
-
- public function setTwoWayKey(string $twoWayKey): void
- {
- $this->twoWayKey = $twoWayKey;
+ return $this->options['twoWayKey'];
}
public function getOnDelete(): string
{
- return $this->onDelete;
- }
-
- public function setOnDelete(string $onDelete): void
- {
- $this->onDelete = $onDelete;
+ return $this->options['onDelete'];
}
public function getSide(): string
{
- return $this->side;
- }
-
- public function setSide(string $side): void
- {
- $this->side = $side;
+ return $this->options['side'];
}
}
diff --git a/src/Migration/Resources/Database/Attributes/Text.php b/src/Migration/Resources/Database/Attributes/Text.php
index 8b4b6ba..6effa0e 100644
--- a/src/Migration/Resources/Database/Attributes/Text.php
+++ b/src/Migration/Resources/Database/Attributes/Text.php
@@ -2,26 +2,67 @@
namespace Utopia\Migration\Resources\Database\Attributes;
+use Utopia\Database\Database;
use Utopia\Migration\Resources\Database\Attribute;
use Utopia\Migration\Resources\Database\Collection;
class Text extends Attribute
{
- protected ?string $default;
-
- protected int $size = 256;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?string $default = null,
+ bool $array = false,
+ int $size = Database::LENGTH_KEY,
+ string $format = '',
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ size: $size,
+ required: $required,
+ default: $default,
+ array: $array,
+ format: $format,
+ );
+ }
/**
- * @param ?string $default
+ * @param array{
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * required: bool,
+ * default: ?string,
+ * array: bool,
+ * size: int,
+ * format: string,
+ * } $array
+ * @return self
*/
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null, int $size = 256)
+ public static function fromArray(array $array): self
{
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- $this->size = $size;
+ return new self(
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ required: $array['required'],
+ default: $array['default'] ?? null,
+ array: $array['array'],
+ size: $array['size'],
+ format: $array['format'],
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_STRING;
}
@@ -31,27 +72,8 @@ public function getSize(): int
return $this->size;
}
- public function setSize(int $size): self
- {
- $this->size = $size;
-
- return $this;
- }
-
- public function getDefault(): ?string
- {
- return $this->default;
- }
-
- public function setDefault(string $default): void
- {
- $this->default = $default;
- }
-
- public function asArray(): array
+ public function getFormat(): string
{
- return array_merge(parent::asArray(), [
- 'size' => $this->size,
- ]);
+ return $this->format;
}
}
diff --git a/src/Migration/Resources/Database/Attributes/URL.php b/src/Migration/Resources/Database/Attributes/URL.php
index 3b76afe..afe80ce 100644
--- a/src/Migration/Resources/Database/Attributes/URL.php
+++ b/src/Migration/Resources/Database/Attributes/URL.php
@@ -5,30 +5,28 @@
use Utopia\Migration\Resources\Database\Attribute;
use Utopia\Migration\Resources\Database\Collection;
-class URL extends Attribute
+class URL extends Text
{
- protected ?string $default;
-
- /**
- * @param ?string $default
- */
- public function __construct(string $key, Collection $collection, bool $required = false, bool $array = false, ?string $default = null)
- {
- parent::__construct($key, $collection, $required, $array);
- $this->default = $default;
- }
-
- public function getDefault(): ?string
- {
- return $this->default;
- }
-
- public function setDefault(string $default): void
- {
- $this->default = $default;
+ public function __construct(
+ string $key,
+ Collection $collection,
+ bool $required = false,
+ ?string $default = null,
+ bool $array = false,
+ int $size = 2000
+ ) {
+ parent::__construct(
+ $key,
+ $collection,
+ required: $required,
+ default: $default,
+ array: $array,
+ size: $size,
+ format: 'url',
+ );
}
- public function getTypeName(): string
+ public function getType(): string
{
return Attribute::TYPE_URL;
}
diff --git a/src/Migration/Resources/Database/Collection.php b/src/Migration/Resources/Database/Collection.php
index 5d03619..fcf79e2 100644
--- a/src/Migration/Resources/Database/Collection.php
+++ b/src/Migration/Resources/Database/Collection.php
@@ -8,28 +8,58 @@
class Collection extends Resource
{
/**
- * @var list
+ * @param Database $database
+ * @param string $name
+ * @param string $id
+ * @param bool $documentSecurity
+ * @param array $permissions
*/
- private array $columns = [];
+ public function __construct(
+ private readonly Database $database,
+ private readonly string $name,
+ string $id,
+ private readonly bool $documentSecurity = false,
+ array $permissions = [],
+ ) {
+ $this->id = $id;
+ $this->permissions = $permissions;
+ }
/**
- * @var list
+ * @param array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * } $array
*/
- private array $indexes = [];
-
- private Database $database;
-
- protected bool $documentSecurity = false;
-
- protected string $name;
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ Database::fromArray($array['database']),
+ id: $array['id'],
+ name: $array['name'],
+ documentSecurity: $array['documentSecurity'],
+ permissions: $array['permissions'] ?? []
+ );
+ }
- public function __construct(Database $database, string $name, string $id, bool $documentSecurity = false, array $permissions = [])
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->database = $database;
- $this->name = $name;
- $this->id = $id;
- $this->documentSecurity = $documentSecurity;
- $this->permissions = $permissions;
+ return array_merge([
+ 'database' => $this->database,
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'documentSecurity' => $this->documentSecurity,
+ 'permissions' => $this->permissions,
+ ]);
}
public static function getName(): string
@@ -47,44 +77,13 @@ public function getDatabase(): Database
return $this->database;
}
- public function setDatabase(Database $database): self
- {
- $this->database = $database;
-
- return $this;
- }
-
public function getCollectionName(): string
{
return $this->name;
}
- public function setCollectionName(string $name): self
- {
- $this->name = $name;
-
- return $this;
- }
-
public function getDocumentSecurity(): bool
{
return $this->documentSecurity;
}
-
- public function setDocumentSecurity(bool $documentSecurity): self
- {
- $this->documentSecurity = $documentSecurity;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'name' => $this->name,
- 'id' => $this->id,
- 'permissions' => $this->permissions,
- 'documentSecurity' => $this->documentSecurity,
- ];
- }
}
diff --git a/src/Migration/Resources/Database/Database.php b/src/Migration/Resources/Database/Database.php
index 804543d..39d8879 100644
--- a/src/Migration/Resources/Database/Database.php
+++ b/src/Migration/Resources/Database/Database.php
@@ -15,17 +15,37 @@
class Database extends Resource
{
+ public function __construct(
+ string $id = '',
+ private readonly string $name = '',
+ ) {
+ $this->id = $id;
+ // Do we need to $this->name = $name;
+ }
+
/**
- * @var list
+ * @param array{
+ * id: string,
+ * name: string,
+ * } $array
*/
- private array $collections = [];
-
- protected string $name;
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ $array['name'],
+ );
+ }
- public function __construct(string $name = '', string $id = '')
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->name = $name;
- $this->id = $id;
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ ];
}
public static function getName(): string
@@ -38,37 +58,8 @@ public function getGroup(): string
return Transfer::GROUP_DATABASES;
}
- public function getDBName(): string
+ public function getDatabaseName(): string
{
return $this->name;
}
-
- /**
- * @return list
- */
- public function getCollections(): array
- {
- return $this->collections;
- }
-
- /**
- * @param list $collections
- */
- public function setCollections(array $collections): self
- {
- $this->collections = $collections;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'name' => $this->name,
- 'id' => $this->id,
- 'collections' => array_map(function ($collection) {
- return $collection->asArray();
- }, $this->collections),
- ];
- }
}
diff --git a/src/Migration/Resources/Database/Document.php b/src/Migration/Resources/Database/Document.php
index 4124bcf..db3d41a 100644
--- a/src/Migration/Resources/Database/Document.php
+++ b/src/Migration/Resources/Database/Document.php
@@ -7,41 +7,70 @@
class Document extends Resource
{
- protected Database $database;
-
- protected Collection $collection;
-
- protected array $data;
-
- public function __construct(string $id, Database $database, Collection $collection, array $data = [], array $permissions = [])
- {
+ /**
+ * @param string $id
+ * @param Collection $collection
+ * @param array $data
+ * @param array $permissions
+ */
+ public function __construct(
+ string $id,
+ private readonly Collection $collection,
+ private readonly array $data = [],
+ array $permissions = []
+ ) {
$this->id = $id;
- $this->database = $database;
- $this->collection = $collection;
- $this->data = $data;
$this->permissions = $permissions;
}
- public static function getName(): string
+ /**
+ * @param array{
+ * id: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * data: array,
+ * permissions: ?array
+ * } $array
+ */
+ public static function fromArray(array $array): self
{
- return Resource::TYPE_DOCUMENT;
+ return new self(
+ $array['id'],
+ Collection::fromArray($array['collection']),
+ $array['data'],
+ $array['permissions'] ?? []
+ );
}
- public function getGroup(): string
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- return Transfer::GROUP_DATABASES;
+ return [
+ 'id' => $this->id,
+ 'collection' => $this->collection,
+ 'data' => $this->data,
+ 'permissions' => $this->permissions,
+ ];
}
- public function getDatabase(): Database
+ public static function getName(): string
{
- return $this->database;
+ return Resource::TYPE_DOCUMENT;
}
- public function setDatabase(Database $database): self
+ public function getGroup(): string
{
- $this->database = $database;
-
- return $this;
+ return Transfer::GROUP_DATABASES;
}
public function getCollection(): Collection
@@ -49,38 +78,11 @@ public function getCollection(): Collection
return $this->collection;
}
- public function setCollection(Collection $collection): self
- {
- $this->collection = $collection;
-
- return $this;
- }
-
- public function getData(): array
- {
- return $this->data;
- }
-
/**
- * Set Data
- *
- * @param array $data
+ * @return array
*/
- public function setData(array $data): self
- {
- $this->data = $data;
-
- return $this;
- }
-
- public function asArray(): array
+ public function getData(): array
{
- return [
- 'id' => $this->id,
- 'database' => $this->database,
- 'collection' => $this->collection,
- 'attributes' => $this->data,
- 'permissions' => $this->permissions,
- ];
+ return $this->data;
}
}
diff --git a/src/Migration/Resources/Database/Index.php b/src/Migration/Resources/Database/Index.php
index bf0381f..59b4c80 100644
--- a/src/Migration/Resources/Database/Index.php
+++ b/src/Migration/Resources/Database/Index.php
@@ -7,33 +7,80 @@
class Index extends Resource
{
- protected string $key;
+ public const string TYPE_UNIQUE = 'unique';
- protected string $type;
+ public const string TYPE_FULLTEXT = 'fulltext';
- protected array $attributes;
+ public const string TYPE_KEY = 'key';
- protected array $orders;
-
- protected Collection $collection;
-
- public const TYPE_UNIQUE = 'unique';
-
- public const TYPE_FULLTEXT = 'fulltext';
+ /**
+ * @param string $id
+ * @param string $key
+ * @param Collection $collection
+ * @param string $type
+ * @param array $attributes
+ * @param array $lengths
+ * @param array $orders
+ */
+ public function __construct(
+ string $id,
+ private readonly string $key,
+ private readonly Collection $collection,
+ private readonly string $type = '',
+ private readonly array $attributes = [],
+ private readonly array $lengths = [],
+ private readonly array $orders = []
+ ) {
+ $this->id = $id;
+ }
- public const TYPE_KEY = 'key';
+ /**
+ * @param array{
+ * id: string,
+ * key: string,
+ * collection: array{
+ * database: array{
+ * id: string,
+ * name: string,
+ * },
+ * name: string,
+ * id: string,
+ * documentSecurity: bool,
+ * permissions: ?array
+ * },
+ * type: string,
+ * attributes: array,
+ * lengths: ?array,
+ * orders: ?array
+ * } $array
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ $array['key'],
+ Collection::fromArray($array['collection']),
+ $array['type'],
+ $array['attributes'],
+ $array['lengths'] ?? [],
+ $array['orders'] ?? []
+ );
+ }
/**
- * @param list $attributes
+ * @return array
*/
- public function __construct(string $id, string $key, Collection $collection, string $type = '', array $attributes = [], array $orders = [])
+ public function jsonSerialize(): array
{
- $this->id = $id;
- $this->key = $key;
- $this->type = $type;
- $this->attributes = $attributes;
- $this->orders = $orders;
- $this->collection = $collection;
+ return [
+ 'id' => $this->getId(),
+ 'key' => $this->key,
+ 'collection' => $this->collection,
+ 'type' => $this->type,
+ 'attributes' => $this->attributes,
+ 'lengths' => $this->lengths,
+ 'orders' => $this->orders,
+ ];
}
public static function getName(): string
@@ -51,71 +98,29 @@ public function getKey(): string
return $this->key;
}
- public function setKey(string $key): self
- {
- $this->key = $key;
-
- return $this;
- }
-
public function getCollection(): Collection
{
return $this->collection;
}
- public function setCollection(Collection $collection): self
- {
- $this->collection = $collection;
-
- return $this;
- }
-
public function getType(): string
{
return $this->type;
}
- public function setType(string $type): self
- {
- $this->type = $type;
-
- return $this;
- }
-
+ /**
+ * @return array
+ */
public function getAttributes(): array
{
return $this->attributes;
}
/**
- * @param list $attributes
+ * @return array
*/
- public function setAttributes(array $attributes): self
- {
- $this->attributes = $attributes;
-
- return $this;
- }
-
public function getOrders(): array
{
return $this->orders;
}
-
- public function setOrders(array $orders): self
- {
- $this->orders = $orders;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'key' => $this->key,
- 'type' => $this->type,
- 'attributes' => $this->attributes,
- 'orders' => $this->orders,
- ];
- }
}
diff --git a/src/Migration/Resources/Functions/Deployment.php b/src/Migration/Resources/Functions/Deployment.php
index 39927c5..5be2bd1 100644
--- a/src/Migration/Resources/Functions/Deployment.php
+++ b/src/Migration/Resources/Functions/Deployment.php
@@ -7,30 +7,52 @@
class Deployment extends Resource
{
- protected Func $func;
-
- protected string $entrypoint;
-
- protected int $size;
-
- protected int $start;
-
- protected int $end;
-
- protected string $data;
+ public function __construct(
+ string $id,
+ private readonly Func $func,
+ private readonly int $size,
+ private readonly string $entrypoint,
+ private int $start = 0,
+ private int $end = 0,
+ private string $data = '',
+ private readonly bool $activated = false
+ ) {
+ $this->id = $id;
+ }
- protected bool $activated;
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ Func::fromArray($array['func']),
+ $array['size'],
+ $array['entrypoint'],
+ $array['start'] ?? 0,
+ $array['end'] ?? 0,
+ $array['data'] ?? '',
+ $array['activated'] ?? false
+ );
+ }
- public function __construct(string $id, Func $func, int $size, string $entrypoint, int $start = 0, int $end = 0, string $data = '', bool $activated = false)
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->id = $id;
- $this->func = $func;
- $this->size = $size;
- $this->entrypoint = $entrypoint;
- $this->start = $start;
- $this->end = $end;
- $this->data = $data;
- $this->activated = $activated;
+ return [
+ 'id' => $this->id,
+ 'func' => $this->func,
+ 'size' => $this->size,
+ 'entrypoint' => $this->entrypoint,
+ 'start' => $this->start,
+ 'end' => $this->end,
+ 'data' => $this->data,
+ 'activated' => $this->activated,
+ ];
}
public static function getName(): string
@@ -48,32 +70,11 @@ public function getFunction(): Func
return $this->func;
}
- public function setFunction(Func $func): self
- {
- $this->func = $func;
-
- return $this;
- }
-
- public function setSize(int $size): self
- {
- $this->size = $size;
-
- return $this;
- }
-
public function getSize(): int
{
return $this->size;
}
- public function setEntrypoint(string $entrypoint): self
- {
- $this->entrypoint = $entrypoint;
-
- return $this;
- }
-
public function getEntrypoint(): string
{
return $this->entrypoint;
@@ -115,28 +116,8 @@ public function getData(): string
return $this->data;
}
- public function setActivated(bool $activated): self
- {
- $this->activated = $activated;
-
- return $this;
- }
-
public function getActivated(): bool
{
return $this->activated;
}
-
- public function asArray(): array
- {
- return [
- 'id' => $this->id,
- 'func' => $this->func->asArray(),
- 'size' => $this->size,
- 'entrypoint' => $this->entrypoint,
- 'start' => $this->start,
- 'end' => $this->end,
- 'activated' => $this->activated,
- ];
- }
}
diff --git a/src/Migration/Resources/Functions/EnvVar.php b/src/Migration/Resources/Functions/EnvVar.php
index c2f7b75..2cd7f97 100644
--- a/src/Migration/Resources/Functions/EnvVar.php
+++ b/src/Migration/Resources/Functions/EnvVar.php
@@ -7,17 +7,40 @@
class EnvVar extends Resource
{
- protected Func $func;
-
- protected string $key;
+ public function __construct(
+ string $id,
+ private readonly Func $func,
+ private readonly string $key,
+ private readonly string $value
+ ) {
+ $this->id = $id;
+ }
- protected string $value;
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ Func::fromArray($array['func']),
+ $array['key'],
+ $array['value']
+ );
+ }
- public function __construct(Func $func, string $key, string $value)
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->func = $func;
- $this->key = $key;
- $this->value = $value;
+ return [
+ 'id' => $this->id,
+ 'func' => $this->func,
+ 'key' => $this->key,
+ 'value' => $this->value,
+ ];
}
public static function getName(): string
@@ -35,43 +58,13 @@ public function getFunc(): Func
return $this->func;
}
- public function setFunc(Func $func): self
- {
- $this->func = $func;
-
- return $this;
- }
-
public function getKey(): string
{
return $this->key;
}
- public function setKey(string $key): self
- {
- $this->key = $key;
-
- return $this;
- }
-
public function getValue(): string
{
return $this->value;
}
-
- public function setValue(string $value): self
- {
- $this->value = $value;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'func' => $this->func->getId(),
- 'key' => $this->key,
- 'value' => $this->value,
- ];
- }
}
diff --git a/src/Migration/Resources/Functions/Func.php b/src/Migration/Resources/Functions/Func.php
index 93c62a3..930f921 100644
--- a/src/Migration/Resources/Functions/Func.php
+++ b/src/Migration/Resources/Functions/Func.php
@@ -7,33 +7,70 @@
class Func extends Resource
{
- protected string $name;
-
- protected array $execute;
-
- protected bool $enabled;
-
- protected string $runtime;
-
- protected array $events;
-
- protected string $schedule;
-
- protected int $timeout;
+ /**
+ * @param string $id
+ * @param string $name
+ * @param string $runtime
+ * @param array $execute
+ * @param bool $enabled
+ * @param array $events
+ * @param string $schedule
+ * @param int $timeout
+ * @param string $activeDeployment
+ * @param string $entrypoint
+ */
+ public function __construct(
+ string $id,
+ private readonly string $name,
+ private readonly string $runtime,
+ private readonly array $execute = [],
+ private readonly bool $enabled = true,
+ private readonly array $events = [],
+ private readonly string $schedule = '',
+ private readonly int $timeout = 0,
+ private readonly string $activeDeployment = '',
+ private readonly string $entrypoint = ''
+ ) {
+ $this->id = $id;
+ }
- protected string $activeDeployment;
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ $array['name'],
+ $array['runtime'],
+ $array['execute'] ?? [],
+ $array['enabled'] ?? true,
+ $array['events'] ?? [],
+ $array['schedule'] ?? '',
+ $array['timeout'] ?? 0,
+ $array['activeDeployment'] ?? '',
+ $array['entrypoint'] ?? ''
+ );
+ }
- public function __construct(string $name, string $id, string $runtime, array $execute = [], bool $enabled = true, array $events = [], string $schedule = '', int $timeout = 0, string $activeDeployment = '')
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->name = $name;
- $this->id = $id;
- $this->execute = $execute;
- $this->enabled = $enabled;
- $this->runtime = $runtime;
- $this->events = $events;
- $this->schedule = $schedule;
- $this->timeout = $timeout;
- $this->activeDeployment = $activeDeployment;
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'execute' => $this->execute,
+ 'enabled' => $this->enabled,
+ 'runtime' => $this->runtime,
+ 'events' => $this->events,
+ 'schedule' => $this->schedule,
+ 'timeout' => $this->timeout,
+ 'activeDeployment' => $this->activeDeployment,
+ 'entrypoint' => $this->entrypoint,
+ ];
}
public static function getName(): string
@@ -51,102 +88,49 @@ public function getFunctionName(): string
return $this->name;
}
+ /**
+ * @return array
+ */
public function getExecute(): array
{
return $this->execute;
}
- public function setExecute(array $execute): self
- {
- $this->execute = $execute;
-
- return $this;
- }
-
public function getEnabled(): bool
{
return $this->enabled;
}
- public function setEnabled(bool $enabled): self
- {
- $this->enabled = $enabled;
-
- return $this;
- }
-
public function getRuntime(): string
{
return $this->runtime;
}
- public function setRuntime(string $runtime): self
- {
- $this->runtime = $runtime;
-
- return $this;
- }
-
+ /**
+ * @return array
+ */
public function getEvents(): array
{
return $this->events;
}
- public function setEvents(array $events): self
- {
- $this->events = $events;
-
- return $this;
- }
-
public function getSchedule(): string
{
return $this->schedule;
}
- public function setSchedule(string $schedule): self
- {
- $this->schedule = $schedule;
-
- return $this;
- }
-
public function getTimeout(): int
{
return $this->timeout;
}
- public function setTimeout(int $timeout): self
- {
- $this->timeout = $timeout;
-
- return $this;
- }
-
public function getActiveDeployment(): string
{
return $this->activeDeployment;
}
- public function setActiveDeployment(string $activeDeployment): self
+ public function getEntrypoint(): string
{
- $this->activeDeployment = $activeDeployment;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'name' => $this->name,
- 'id' => $this->id,
- 'execute' => $this->execute,
- 'enabled' => $this->enabled,
- 'runtime' => $this->runtime,
- 'events' => $this->events,
- 'schedule' => $this->schedule,
- 'timeout' => $this->timeout,
- 'activeDeployment' => $this->activeDeployment,
- ];
+ return $this->entrypoint;
}
}
diff --git a/src/Migration/Resources/Storage/Bucket.php b/src/Migration/Resources/Storage/Bucket.php
index 6d26f00..cd6fdf0 100644
--- a/src/Migration/Resources/Storage/Bucket.php
+++ b/src/Migration/Resources/Storage/Bucket.php
@@ -7,36 +7,74 @@
class Bucket extends Resource
{
- protected ?bool $fileSecurity;
-
- protected string $name;
-
- protected ?bool $enabled;
-
- protected ?int $maxFileSize;
-
- protected ?array $allowedFileExtensions;
-
- protected ?string $compression;
-
- protected ?bool $encryption;
-
- protected ?bool $antiVirus;
-
- protected bool $updateLimits = false;
-
- public function __construct(string $id = '', string $name = '', array $permissions = [], bool $fileSecurity = false, bool $enabled = false, ?int $maxFileSize = null, array $allowedFileExtensions = [], string $compression = 'none', bool $encryption = false, bool $antiVirus = false, bool $updateLimits = false)
- {
+ /**
+ * @param string $id
+ * @param string $name
+ * @param array $permissions
+ * @param bool $fileSecurity
+ * @param bool $enabled
+ * @param int|null $maxFileSize
+ * @param array $allowedFileExtensions
+ * @param string $compression
+ * @param bool $encryption
+ * @param bool $antiVirus
+ * @param bool $updateLimits
+ */
+ public function __construct(
+ string $id = '',
+ private readonly string $name = '',
+ array $permissions = [],
+ private readonly bool $fileSecurity = false,
+ private readonly bool $enabled = false,
+ private readonly ?int $maxFileSize = null,
+ private readonly array $allowedFileExtensions = [],
+ private readonly string $compression = 'none',
+ private readonly bool $encryption = false,
+ private readonly bool $antiVirus = false,
+ private readonly bool $updateLimits = false,
+ ) {
$this->id = $id;
- $this->name = $name;
$this->permissions = $permissions;
- $this->fileSecurity = $fileSecurity;
- $this->enabled = $enabled;
- $this->maxFileSize = $maxFileSize;
- $this->allowedFileExtensions = $allowedFileExtensions;
- $this->compression = $compression;
- $this->encryption = $encryption;
- $this->antiVirus = $antiVirus;
+ }
+
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ $array['name'] ?? '',
+ $array['permissions'] ?? [],
+ $array['fileSecurity'] ?? false,
+ $array['enabled'] ?? false,
+ $array['maxFileSize'] ?? null,
+ $array['allowedFileExtensions'] ?? [],
+ $array['compression'] ?? 'none',
+ $array['encryption'] ?? false,
+ $array['antiVirus'] ?? false,
+ $array['updateLimits'] ?? false
+ );
+ }
+
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
+ {
+ return [
+ 'id' => $this->id,
+ 'name' => $this->name,
+ 'fileSecurity' => $this->fileSecurity,
+ 'enabled' => $this->enabled,
+ 'maxFileSize' => $this->maxFileSize,
+ 'allowedFileExtensions' => $this->allowedFileExtensions,
+ 'compression' => $this->compression,
+ 'encryption' => $this->encryption,
+ 'antiVirus' => $this->antiVirus,
+ 'updateLimits' => $this->updateLimits,
+ ];
}
public static function getName(): string
@@ -54,122 +92,43 @@ public function getFileSecurity(): bool
return $this->fileSecurity;
}
- public function setFileSecurity(bool $fileSecurity): self
- {
- $this->fileSecurity = $fileSecurity;
-
- return $this;
- }
-
public function getBucketName(): string
{
return $this->name;
}
- public function setBucketName(string $name): self
- {
- $this->name = $name;
-
- return $this;
- }
public function getEnabled(): bool
{
return $this->enabled;
}
- public function setEnabled(bool $enabled): self
- {
- $this->enabled = $enabled;
-
- return $this;
- }
-
public function getMaxFileSize(): ?int
{
return $this->maxFileSize;
}
- public function setMaxFileSize(?int $maxFileSize): self
- {
- $this->maxFileSize = $maxFileSize;
-
- return $this;
- }
+ /**
+ * @return array
+ */
public function getAllowedFileExtensions(): array
{
return $this->allowedFileExtensions;
}
- public function setAllowedFileExtensions(array $allowedFileExtensions): self
- {
- $this->allowedFileExtensions = $allowedFileExtensions;
-
- return $this;
- }
-
public function getCompression(): string
{
return $this->compression;
}
- public function setCompression(string $compression): self
- {
- $this->compression = $compression;
-
- return $this;
- }
-
public function getEncryption(): bool
{
return $this->encryption;
}
- public function setEncryption(bool $encryption): self
- {
- $this->encryption = $encryption;
-
- return $this;
- }
-
public function getAntiVirus(): bool
{
return $this->antiVirus;
}
-
- public function setAntiVirus(bool $antiVirus): self
- {
- $this->antiVirus = $antiVirus;
-
- return $this;
- }
-
- public function getUpdateLimits(): bool
- {
- return $this->updateLimits;
- }
-
- public function setUpdateLimits(bool $updateLimits): self
- {
- $this->updateLimits = $updateLimits;
-
- return $this;
- }
-
- public function asArray(): array
- {
- return [
- 'id' => $this->id,
- 'permissions' => $this->permissions,
- 'fileSecurity' => $this->fileSecurity,
- 'name' => $this->name,
- 'enabled' => $this->enabled,
- 'maxFileSize' => $this->maxFileSize,
- 'allowedFileExtensions' => $this->allowedFileExtensions,
- 'compression' => $this->compression,
- 'encryption' => $this->encryption,
- 'antiVirus' => $this->antiVirus,
- ];
- }
}
diff --git a/src/Migration/Resources/Storage/File.php b/src/Migration/Resources/Storage/File.php
index 99914e7..ba47ec0 100644
--- a/src/Migration/Resources/Storage/File.php
+++ b/src/Migration/Resources/Storage/File.php
@@ -7,34 +7,70 @@
class File extends Resource
{
- protected Bucket $bucket;
-
- protected string $name;
-
- protected string $signature;
-
- protected string $mimeType;
-
- protected int $size;
-
- protected string $data;
-
- protected int $start;
+ /**
+ * @param string $id
+ * @param Bucket $bucket
+ * @param string $name
+ * @param string $signature
+ * @param string $mimeType
+ * @param array $permissions
+ * @param int $size
+ * @param string $data
+ * @param int $start
+ * @param int $end
+ */
+ public function __construct(
+ string $id,
+ private readonly Bucket $bucket,
+ private readonly string $name = '',
+ private readonly string $signature = '',
+ private readonly string $mimeType = '',
+ array $permissions = [],
+ private readonly int $size = 0,
+ private string $data = '',
+ private int $start = 0,
+ private int $end = 0
+ ) {
+ $this->id = $id;
+ $this->permissions = $permissions;
+ }
- protected int $end;
+ /**
+ * @param array $array
+ * @return self
+ */
+ public static function fromArray(array $array): self
+ {
+ return new self(
+ $array['id'],
+ Bucket::fromArray($array['bucket']),
+ $array['name'] ?? '',
+ $array['signature'] ?? '',
+ $array['mimeType'] ?? '',
+ $array['permissions'] ?? [],
+ $array['size'] ?? 0,
+ $array['data'] ?? '',
+ $array['start'] ?? 0,
+ $array['end'] ?? 0
+ );
+ }
- public function __construct(string $id = '', ?Bucket $bucket = null, string $name = '', string $signature = '', string $mimeType = '', array $permissions = [], int $size = 0, string $data = '', int $start = 0, int $end = 0)
+ /**
+ * @return array
+ */
+ public function jsonSerialize(): array
{
- $this->id = $id;
- $this->bucket = $bucket;
- $this->name = $name;
- $this->signature = $signature;
- $this->mimeType = $mimeType;
- $this->permissions = $permissions;
- $this->size = $size;
- $this->data = $data;
- $this->start = $start;
- $this->end = $end;
+ return [
+ 'id' => $this->id,
+ 'bucket' => $this->bucket,
+ 'name' => $this->name,
+ 'signature' => $this->signature,
+ 'mimeType' => $this->mimeType,
+ 'permissions' => $this->permissions,
+ 'size' => $this->size,
+ 'start' => $this->start,
+ 'end' => $this->end,
+ ];
}
public static function getName(): string
@@ -52,25 +88,11 @@ public function getBucket(): Bucket
return $this->bucket;
}
- public function setBucket(Bucket $bucket): self
- {
- $this->bucket = $bucket;
-
- return $this;
- }
-
public function getFileName(): string
{
return $this->name;
}
- public function setName(string $name): self
- {
- $this->name = $name;
-
- return $this;
- }
-
public function getSize(): int
{
return $this->size;
@@ -81,25 +103,11 @@ public function getSignature(): string
return $this->signature;
}
- public function setSignature(string $signature): self
- {
- $this->signature = $signature;
-
- return $this;
- }
-
public function getMimeType(): string
{
return $this->mimeType;
}
- public function setMimeType(string $mimeType): self
- {
- $this->mimeType = $mimeType;
-
- return $this;
- }
-
public function getData(): string
{
return $this->data;
@@ -145,19 +153,4 @@ public function getChunkSize(): int
{
return $this->end - $this->start;
}
-
- public function asArray(): array
- {
- return [
- 'id' => $this->id,
- 'bucket' => $this->bucket->getId(),
- 'name' => $this->name,
- 'signature' => $this->signature,
- 'mimeType' => $this->mimeType,
- 'permissions' => $this->permissions,
- 'size' => $this->size,
- 'start' => $this->start,
- 'end' => $this->end,
- ];
- }
}
diff --git a/src/Migration/Source.php b/src/Migration/Source.php
index 45ba29a..46333b6 100644
--- a/src/Migration/Source.php
+++ b/src/Migration/Source.php
@@ -4,6 +4,9 @@
abstract class Source extends Target
{
+ /**
+ * @var callable(array): void $transferCallback
+ */
protected $transferCallback;
/**
@@ -11,6 +14,10 @@ abstract class Source extends Target
*/
public array $previousReport = [];
+ /**
+ * @param array $resources
+ * @return void
+ */
public function callback(array $resources): void
{
($this->transferCallback)($resources);
@@ -19,48 +26,53 @@ public function callback(array $resources): void
/**
* Transfer Resources into destination
*
- * @param string[] $resources Resources to transfer
- * @param callable $callback Callback to run after transfer
+ * @param array $resources Resources to transfer
+ * @param callable $callback Callback to run after transfer
+ * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource
*/
- public function run(array $resources, callable $callback): void
+ public function run(array $resources, callable $callback, string $rootResourceId = '', string $rootResourceType = ''): void
{
+ $this->rootResourceId = $rootResourceId;
+ $this->rootResourceType = $rootResourceType;
+
$this->transferCallback = function (array $returnedResources) use ($callback, $resources) {
- $prunedResurces = [];
+ $prunedResources = [];
foreach ($returnedResources as $resource) {
- /** @var resource $resource */
+ /** @var Resource $resource */
if (! in_array($resource->getName(), $resources)) {
$resource->setStatus(Resource::STATUS_SKIPPED);
} else {
- $prunedResurces[] = $resource;
+ $prunedResources[] = $resource;
}
}
$callback($returnedResources);
- $this->cache->addAll($prunedResurces);
+ $this->cache->addAll($prunedResources);
};
- $this->exportResources($resources, 100);
+ $this->exportResources($resources);
}
/**
* Export Resources
*
- * @param string[] $resources Resources to export
- * @param int $batchSize Max 100
+ * @param array $resources Resources to export
*/
- public function exportResources(array $resources, int $batchSize)
+ public function exportResources(array $resources): void
{
// Convert Resources back into their relevant groups
+ $batchSize = $this->getBatchSize();
+
$groups = [];
foreach ($resources as $resource) {
- if (in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) {
+ if (\in_array($resource, Transfer::GROUP_AUTH_RESOURCES)) {
$groups[Transfer::GROUP_AUTH][] = $resource;
- } elseif (in_array($resource, Transfer::GROUP_DATABASES_RESOURCES)) {
+ } elseif (\in_array($resource, Transfer::GROUP_DATABASES_RESOURCES)) {
$groups[Transfer::GROUP_DATABASES][] = $resource;
- } elseif (in_array($resource, Transfer::GROUP_STORAGE_RESOURCES)) {
+ } elseif (\in_array($resource, Transfer::GROUP_STORAGE_RESOURCES)) {
$groups[Transfer::GROUP_STORAGE][] = $resource;
- } elseif (in_array($resource, Transfer::GROUP_FUNCTIONS_RESOURCES)) {
+ } elseif (\in_array($resource, Transfer::GROUP_FUNCTIONS_RESOURCES)) {
$groups[Transfer::GROUP_FUNCTIONS][] = $resource;
}
}
@@ -87,36 +99,40 @@ public function exportResources(array $resources, int $batchSize)
}
}
}
+ public function getBatchSize(): int
+ {
+ return 100;
+ }
/**
* Export Auth Group
*
* @param int $batchSize Max 100
- * @param string[] $resources Resources to export
+ * @param array $resources Resources to export
*/
- abstract protected function exportGroupAuth(int $batchSize, array $resources);
+ abstract protected function exportGroupAuth(int $batchSize, array $resources): void;
/**
* Export Databases Group
*
* @param int $batchSize Max 100
- * @param string[] $resources Resources to export
+ * @param array $resources Resources to export
*/
- abstract protected function exportGroupDatabases(int $batchSize, array $resources);
+ abstract protected function exportGroupDatabases(int $batchSize, array $resources): void;
/**
* Export Storage Group
*
* @param int $batchSize Max 5
- * @param string[] $resources Resources to export
+ * @param array $resources Resources to export
*/
- abstract protected function exportGroupStorage(int $batchSize, array $resources);
+ abstract protected function exportGroupStorage(int $batchSize, array $resources): void;
/**
* Export Functions Group
*
* @param int $batchSize Max 100
- * @param string[] $resources Resources to export
+ * @param array $resources Resources to export
*/
- abstract protected function exportGroupFunctions(int $batchSize, array $resources);
+ abstract protected function exportGroupFunctions(int $batchSize, array $resources): void;
}
diff --git a/src/Migration/Sources/Appwrite.php b/src/Migration/Sources/Appwrite.php
index 00abd19..07789a4 100644
--- a/src/Migration/Sources/Appwrite.php
+++ b/src/Migration/Sources/Appwrite.php
@@ -2,6 +2,7 @@
namespace Utopia\Migration\Sources;
+use Appwrite\AppwriteException;
use Appwrite\Client;
use Appwrite\Query;
use Appwrite\Services\Databases;
@@ -9,6 +10,7 @@
use Appwrite\Services\Storage;
use Appwrite\Services\Teams;
use Appwrite\Services\Users;
+use Utopia\Database\Database as UtopiaDatabase;
use Utopia\Migration\Exception;
use Utopia\Migration\Resource;
use Utopia\Migration\Resources\Auth\Hash;
@@ -40,25 +42,36 @@
class Appwrite extends Source
{
- /**
- * @var Client|null
- */
- protected $client = null;
+ protected Client $client;
- protected string $project = '';
+ private Users $users;
- protected string $key = '';
+ private Teams $teams;
- public function __construct(string $project, string $endpoint, string $key)
- {
+ private Databases $database;
+
+ private Storage $storage;
+
+ private Functions $functions;
+
+ public function __construct(
+ protected string $project,
+ string $endpoint,
+ protected string $key
+ ) {
$this->client = (new Client())
->setEndpoint($endpoint)
->setProject($project)
->setKey($key);
+ $this->users = new Users($this->client);
+ $this->teams = new Teams($this->client);
+ $this->database = new Databases($this->client);
+ $this->storage = new Storage($this->client);
+ $this->functions = new Functions($this->client);
+
$this->endpoint = $endpoint;
- $this->project = $project;
- $this->key = $key;
+
$this->headers['X-Appwrite-Project'] = $this->project;
$this->headers['X-Appwrite-Key'] = $this->key;
}
@@ -68,6 +81,9 @@ public static function getName(): string
return 'Appwrite';
}
+ /**
+ * @return array
+ */
public static function getSupportedResources(): array
{
return [
@@ -96,109 +112,129 @@ public static function getSupportedResources(): array
];
}
+ /**
+ * @param array $resources
+ * @return array
+ *
+ * @throws \Exception
+ */
public function report(array $resources = []): array
{
$report = [];
- $currentPermission = '';
if (empty($resources)) {
$resources = $this->getSupportedResources();
}
- $usersClient = new Users($this->client);
- $teamsClient = new Teams($this->client);
- $databaseClient = new Databases($this->client);
- $storageClient = new Storage($this->client);
- $functionsClient = new Functions($this->client);
-
// Auth
try {
- $currentPermission = 'users.read';
- if (in_array(Resource::TYPE_USER, $resources)) {
- $report[Resource::TYPE_USER] = $usersClient->list()['total'];
+ $scope = 'users.read';
+ if (\in_array(Resource::TYPE_USER, $resources)) {
+ $report[Resource::TYPE_USER] = $this->users->list()['total'];
}
- $currentPermission = 'teams.read';
- if (in_array(Resource::TYPE_TEAM, $resources)) {
- $report[Resource::TYPE_TEAM] = $teamsClient->list()['total'];
+ $scope = 'teams.read';
+ if (\in_array(Resource::TYPE_TEAM, $resources)) {
+ $report[Resource::TYPE_TEAM] = $this->teams->list()['total'];
}
- if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) {
+ if (\in_array(Resource::TYPE_MEMBERSHIP, $resources)) {
$report[Resource::TYPE_MEMBERSHIP] = 0;
- $teams = $teamsClient->list()['teams'];
+ $teams = $this->teams->list()['teams'];
foreach ($teams as $team) {
- $report[Resource::TYPE_MEMBERSHIP] += $teamsClient->listMemberships($team['$id'], [Query::limit(1)])['total'];
+ $report[Resource::TYPE_MEMBERSHIP] += $this->teams->listMemberships(
+ $team['$id'],
+ [Query::limit(1)]
+ )['total'];
}
}
// Databases
- $currentPermission = 'databases.read';
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
- $report[Resource::TYPE_DATABASE] = $databaseClient->list()['total'];
+ $scope = 'databases.read';
+ if (\in_array(Resource::TYPE_DATABASE, $resources)) {
+ $report[Resource::TYPE_DATABASE] = $this->database->list()['total'];
}
- $currentPermission = 'collections.read';
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
+ $scope = 'collections.read';
+ if (\in_array(Resource::TYPE_COLLECTION, $resources)) {
$report[Resource::TYPE_COLLECTION] = 0;
- $databases = $databaseClient->list()['databases'];
+ $databases = $this->database->list()['databases'];
foreach ($databases as $database) {
- $report[Resource::TYPE_COLLECTION] += $databaseClient->listCollections($database['$id'], [Query::limit(1)])['total'];
+ $report[Resource::TYPE_COLLECTION] += $this->database->listCollections(
+ $database['$id'],
+ [Query::limit(1)]
+ )['total'];
}
}
- $currentPermission = 'documents.read';
- if (in_array(Resource::TYPE_DOCUMENT, $resources)) {
+ $scope = 'documents.read';
+ if (\in_array(Resource::TYPE_DOCUMENT, $resources)) {
$report[Resource::TYPE_DOCUMENT] = 0;
- $databases = $databaseClient->list()['databases'];
+ $databases = $this->database->list()['databases'];
foreach ($databases as $database) {
- $collections = $databaseClient->listCollections($database['$id'])['collections'];
+ $collections = $this->database->listCollections($database['$id'])['collections'];
foreach ($collections as $collection) {
- $report[Resource::TYPE_DOCUMENT] += $databaseClient->listDocuments($database['$id'], $collection['$id'], [Query::limit(1)])['total'];
+ $report[Resource::TYPE_DOCUMENT] += $this->database->listDocuments(
+ $database['$id'],
+ $collection['$id'],
+ [Query::limit(1)]
+ )['total'];
}
}
}
- $currentPermission = 'attributes.read';
- if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
+ $scope = 'attributes.read';
+ if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
$report[Resource::TYPE_ATTRIBUTE] = 0;
- $databases = $databaseClient->list()['databases'];
+ $databases = $this->database->list()['databases'];
foreach ($databases as $database) {
- $collections = $databaseClient->listCollections($database['$id'])['collections'];
+ $collections = $this->database->listCollections($database['$id'])['collections'];
foreach ($collections as $collection) {
- $report[Resource::TYPE_ATTRIBUTE] += $databaseClient->listAttributes($database['$id'], $collection['$id'])['total'];
+ $report[Resource::TYPE_ATTRIBUTE] += $this->database->listAttributes(
+ $database['$id'],
+ $collection['$id']
+ )['total'];
}
}
}
- $currentPermission = 'indexes.read';
- if (in_array(Resource::TYPE_INDEX, $resources)) {
+ $scope = 'indexes.read';
+ if (\in_array(Resource::TYPE_INDEX, $resources)) {
$report[Resource::TYPE_INDEX] = 0;
- $databases = $databaseClient->list()['databases'];
+ $databases = $this->database->list()['databases'];
foreach ($databases as $database) {
- $collections = $databaseClient->listCollections($database['$id'])['collections'];
+ $collections = $this->database->listCollections($database['$id'])['collections'];
foreach ($collections as $collection) {
- $report[Resource::TYPE_INDEX] += $databaseClient->listIndexes($database['$id'], $collection['$id'])['total'];
+ $report[Resource::TYPE_INDEX] += $this->database->listIndexes(
+ $database['$id'],
+ $collection['$id']
+ )['total'];
}
}
}
// Storage
- $currentPermission = 'buckets.read';
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
- $report[Resource::TYPE_BUCKET] = $storageClient->listBuckets()['total'];
+ $scope = 'buckets.read';
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
+ $report[Resource::TYPE_BUCKET] = $this->storage->listBuckets()['total'];
}
- $currentPermission = 'files.read';
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ $scope = 'files.read';
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$report[Resource::TYPE_FILE] = 0;
$report['size'] = 0;
$buckets = [];
$lastBucket = null;
while (true) {
- $currentBuckets = $storageClient->listBuckets($lastBucket ? [Query::cursorAfter($lastBucket)] : [Query::limit(20)])['buckets'];
+ $currentBuckets = $this->storage->listBuckets(
+ $lastBucket
+ ? [Query::cursorAfter($lastBucket)]
+ : [Query::limit(20)]
+ )['buckets'];
+
$buckets = array_merge($buckets, $currentBuckets);
- $lastBucket = $buckets[count($buckets) - 1]['$id'];
+ $lastBucket = $buckets[count($buckets) - 1]['$id'] ?? null;
if (count($currentBuckets) < 20) {
break;
@@ -210,7 +246,13 @@ public function report(array $resources = []): array
$lastFile = null;
while (true) {
- $currentFiles = $storageClient->listFiles($bucket['$id'], $lastFile ? [Query::cursorAfter($lastFile)] : [Query::limit(20)])['files'];
+ $currentFiles = $this->storage->listFiles(
+ $bucket['$id'],
+ $lastFile
+ ? [Query::cursorAfter($lastFile)]
+ : [Query::limit(20)]
+ )['files'];
+
$files = array_merge($files, $currentFiles);
$lastFile = $files[count($files) - 1]['$id'];
@@ -221,44 +263,54 @@ public function report(array $resources = []): array
$report[Resource::TYPE_FILE] += count($files);
foreach ($files as $file) {
- $report['size'] += $storageClient->getFile($bucket['$id'], $file['$id'])['sizeOriginal'];
+ $report['size'] += $this->storage->getFile(
+ $bucket['$id'],
+ $file['$id']
+ )['sizeOriginal'];
}
}
$report['size'] = $report['size'] / 1000 / 1000; // MB
}
// Functions
- $currentPermission = 'functions.read';
- if (in_array(Resource::TYPE_FUNCTION, $resources)) {
- $report[Resource::TYPE_FUNCTION] = $functionsClient->list()['total'];
+ $scope = 'functions.read';
+ if (\in_array(Resource::TYPE_FUNCTION, $resources)) {
+ $report[Resource::TYPE_FUNCTION] = $this->functions->list()['total'];
}
- if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) {
+ if (\in_array(Resource::TYPE_DEPLOYMENT, $resources)) {
$report[Resource::TYPE_DEPLOYMENT] = 0;
- $functions = $functionsClient->list()['functions'];
+ $functions = $this->functions->list()['functions'];
foreach ($functions as $function) {
- if (! empty($function['deployment'])) {
+ if (!empty($function['deployment'])) {
$report[Resource::TYPE_DEPLOYMENT] += 1;
}
}
}
- if (in_array(Resource::TYPE_ENVIRONMENT_VARIABLE, $resources)) {
+ if (\in_array(Resource::TYPE_ENVIRONMENT_VARIABLE, $resources)) {
$report[Resource::TYPE_ENVIRONMENT_VARIABLE] = 0;
- $functions = $functionsClient->list()['functions'];
+ $functions = $this->functions->list()['functions'];
foreach ($functions as $function) {
- $report[Resource::TYPE_ENVIRONMENT_VARIABLE] += $functionsClient->listVariables($function['$id'])['total'];
+ $report[Resource::TYPE_ENVIRONMENT_VARIABLE] += $this->functions->listVariables($function['$id'])['total'];
}
}
- $report['version'] = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version'];
+ $report['version'] = $this->call(
+ 'GET',
+ '/health/version',
+ [
+ 'X-Appwrite-Key' => '',
+ 'X-Appwrite-Project' => '',
+ ]
+ )['version'];
$this->previousReport = $report;
return $report;
} catch (\Throwable $e) {
if ($e->getCode() === 403) {
- throw new \Exception("Missing Permission: {$currentPermission}.");
+ throw new \Exception("Missing scope: $scope.");
} else {
throw new \Exception($e->getMessage());
}
@@ -268,71 +320,76 @@ public function report(array $resources = []): array
/**
* Export Auth Resources
*
- * @param int $batchSize Max 100
- * @param string[] $resources
- * @return void
+ * @param int $batchSize Max 100
+ * @param array $resources
*/
- protected function exportGroupAuth(int $batchSize, array $resources)
+ protected function exportGroupAuth(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_USER, $resources)) {
+ if (\in_array(Resource::TYPE_USER, $resources)) {
$this->exportUsers($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_USER,
Transfer::GROUP_AUTH,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
try {
- if (in_array(Resource::TYPE_TEAM, $resources)) {
+ if (\in_array(Resource::TYPE_TEAM, $resources)) {
$this->exportTeams($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_TEAM,
Transfer::GROUP_AUTH,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
try {
- if (in_array(Resource::TYPE_MEMBERSHIP, $resources)) {
+ if (\in_array(Resource::TYPE_MEMBERSHIP, $resources)) {
$this->exportMemberships($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_MEMBERSHIP,
Transfer::GROUP_AUTH,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
}
- private function exportUsers(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportUsers(int $batchSize): void
{
- $usersClient = new Users($this->client);
$lastDocument = null;
- // Export Users
while (true) {
$users = [];
$queries = [Query::limit($batchSize)];
+ if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_USER) {
+ $queries[] = Query::equal('$id', $this->rootResourceId);
+ $queries[] = Query::limit(1);
+ }
+
if ($lastDocument) {
$queries[] = Query::cursorAfter($lastDocument);
}
- $response = $usersClient->list($queries);
+ $response = $this->users->list($queries);
if ($response['total'] == 0) {
break;
}
@@ -348,7 +405,7 @@ private function exportUsers(int $batchSize)
'',
$user['emailVerification'] ?? false,
$user['phoneVerification'] ?? false,
- ! $user['status'],
+ !$user['status'],
$user['prefs'] ?? [],
);
@@ -363,22 +420,29 @@ private function exportUsers(int $batchSize)
}
}
- private function exportTeams(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportTeams(int $batchSize): void
{
- $teamsClient = new Teams($this->client);
+ $this->teams = new Teams($this->client);
$lastDocument = null;
- // Export Teams
while (true) {
$teams = [];
$queries = [Query::limit($batchSize)];
+ if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_TEAM) {
+ $queries[] = Query::equal('$id', $this->rootResourceId);
+ $queries[] = Query::limit(1);
+ }
+
if ($lastDocument) {
$queries[] = Query::cursorAfter($lastDocument);
}
- $response = $teamsClient->list($queries);
+ $response = $this->teams->list($queries);
if ($response['total'] == 0) {
break;
}
@@ -401,16 +465,18 @@ private function exportTeams(int $batchSize)
}
}
- private function exportMemberships(int $batchSize)
+ /**
+ * @throws AppwriteException
+ * @throws \Exception
+ */
+ private function exportMemberships(int $batchSize): void
{
- $teamsClient = new Teams($this->client);
-
- // Export Memberships
$cacheTeams = $this->cache->get(Team::getName());
- /** @var array - array where key is user ID */
+
+ /** @var array $cacheUsers */
$cacheUsers = [];
+
foreach ($this->cache->get(User::getName()) as $cacheUser) {
- /** @var User $cacheUser */
$cacheUsers[$cacheUser->getId()] = $cacheUser;
}
@@ -427,7 +493,7 @@ private function exportMemberships(int $batchSize)
$queries[] = Query::cursorAfter($lastDocument);
}
- $response = $teamsClient->listMemberships($team->getId(), $queries);
+ $response = $this->teams->listMemberships($team->getId(), $queries);
if ($response['total'] == 0) {
break;
@@ -440,6 +506,7 @@ private function exportMemberships(int $batchSize)
}
$memberships[] = new Membership(
+ $membership['$id'],
$team,
$user,
$membership['roles'],
@@ -458,10 +525,10 @@ private function exportMemberships(int $batchSize)
}
}
- protected function exportGroupDatabases(int $batchSize, array $resources)
+ protected function exportGroupDatabases(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
+ if (\in_array(Resource::TYPE_DATABASE, $resources)) {
$this->exportDatabases($batchSize);
}
} catch (\Throwable $e) {
@@ -469,15 +536,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_DATABASE,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
+ if (\in_array(Resource::TYPE_COLLECTION, $resources)) {
$this->exportCollections($batchSize);
}
} catch (\Throwable $e) {
@@ -485,15 +552,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_COLLECTION,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
+ if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
$this->exportAttributes($batchSize);
}
} catch (\Throwable $e) {
@@ -501,15 +568,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_ATTRIBUTE,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_INDEX, $resources)) {
+ if (\in_array(Resource::TYPE_INDEX, $resources)) {
$this->exportIndexes($batchSize);
}
} catch (\Throwable $e) {
@@ -517,15 +584,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_INDEX,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_DOCUMENT, $resources)) {
+ if (\in_array(Resource::TYPE_DOCUMENT, $resources)) {
$this->exportDocuments($batchSize);
}
} catch (\Throwable $e) {
@@ -533,38 +600,19 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_DOCUMENT,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
}
- public function stripMetadata(array $document, bool $root = true)
- {
- if ($root) {
- unset($document['$id']);
- }
-
- unset($document['$permissions']);
- unset($document['$collectionId']);
- unset($document['$updatedAt']);
- unset($document['$createdAt']);
- unset($document['$databaseId']);
-
- foreach ($document as $key => $value) {
- if (is_array($value)) {
- $document[$key] = $this->stripMetadata($value, false);
- }
- }
-
- return $document;
- }
-
- private function exportDocuments(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportDocuments(int $batchSize): void
{
- $databaseClient = new Databases($this->client);
$collections = $this->cache->get(Collection::getName());
foreach ($collections as $collection) {
@@ -580,17 +628,67 @@ private function exportDocuments(int $batchSize)
$queries[] = Query::cursorAfter($lastDocument);
}
- $response = $databaseClient->listDocuments(
+ $selects = ['*', '$id', '$permissions', '$updatedAt', '$createdAt']; // We want Relations flat!
+ $manyToMany = [];
+
+ $attributes = $this->cache->get(Attribute::getName());
+ foreach ($attributes as $attribute) {
+ /** @var Attribute|Relationship $attribute */
+ if (
+ $attribute->getCollection()->getId() === $collection->getId() &&
+ $attribute->getType() === Attribute::TYPE_RELATIONSHIP &&
+ $attribute->getSide() === 'parent' &&
+ $attribute->getRelationType() == 'manyToMany'
+ ) {
+ /**
+ * Blockers:
+ * we should use but Does not work properly:
+ * $selects[] = $attribute->getKey() . '.$id';
+ * when selecting for a relation we get all relations not just the one we were asking.
+ * when selecting for a relation like select(*, relation.$id) , all relations get resolve
+ */
+ $manyToMany[] = $attribute->getKey();
+ }
+ }
+
+ $queries[] = Query::select($selects);
+
+ $response = $this->database->listDocuments(
$collection->getDatabase()->getId(),
$collection->getId(),
$queries
);
foreach ($response['documents'] as $document) {
+ // HACK: Handle many to many
+ if(!empty($manyToMany)) {
+ $stack = ['$id']; // Adding $id because we can't select only relations
+ foreach ($manyToMany as $relation) {
+ $stack[] = $relation . '.$id';
+ }
+
+ $doc = $this->database->getDocument(
+ $collection->getDatabase()->getId(),
+ $collection->getId(),
+ $document['$id'],
+ [Query::select($stack)]
+ );
+
+ foreach ($manyToMany as $key) {
+ $document[$key] = [];
+ foreach ($doc[$key] as $relationDocument) {
+ $document[$key][] = $relationDocument['$id'];
+ }
+ }
+ }
+
$id = $document['$id'];
$permissions = $document['$permissions'];
- $document = $this->stripMetadata($document);
+ unset($document['$id']);
+ unset($document['$permissions']);
+ unset($document['$collectionId']);
+ unset($document['$databaseId']);
// Certain Appwrite versions allowed for data to be required but null
// This isn't allowed in modern versions so we need to remove it by comparing their attributes and replacing it with default value.
@@ -601,8 +699,8 @@ private function exportDocuments(int $batchSize)
continue;
}
- if ($attribute->getRequired() && ! isset($document[$attribute->getKey()])) {
- switch ($attribute->getTypeName()) {
+ if ($attribute->isRequired() && !isset($document[$attribute->getKey()])) {
+ switch ($attribute->getType()) {
case Attribute::TYPE_BOOLEAN:
$document[$attribute->getKey()] = false;
break;
@@ -616,7 +714,7 @@ private function exportDocuments(int $batchSize)
$document[$attribute->getKey()] = 0.0;
break;
case Attribute::TYPE_DATETIME:
- $document[$attribute->getKey()] = 0;
+ $document[$attribute->getKey()] = '1970-01-01 00:00:00.000';
break;
case Attribute::TYPE_URL:
$document[$attribute->getKey()] = 'http://null';
@@ -625,15 +723,13 @@ private function exportDocuments(int $batchSize)
}
}
- $cleanData = $this->stripMetadata($document);
-
$documents[] = new Document(
$id,
- $collection->getDatabase(),
$collection,
- $cleanData,
+ $document,
$permissions
);
+
$lastDocument = $id;
}
@@ -646,148 +742,148 @@ private function exportDocuments(int $batchSize)
}
}
+ /**
+ * @throws \Exception
+ */
private function convertAttribute(array $value, Collection $collection): Attribute
{
switch ($value['type']) {
case 'string':
- if (! isset($value['format'])) {
+ if (!isset($value['format'])) {
return new Text(
$value['key'],
$collection,
- $value['required'],
- $value['array'],
- $value['default'],
- $value['size'] ?? 0
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ size: $value['size'] ?? 0
);
}
- switch ($value['format']) {
- case 'email':
- return new Email(
- $value['key'],
- $collection,
- $value['required'],
- $value['array'],
- $value['default']
- );
- case 'enum':
- return new Enum(
- $value['key'],
- $collection,
- $value['elements'],
- $value['required'],
- $value['array'],
- $value['default']
- );
- case 'url':
- return new URL(
- $value['key'],
- $collection,
- $value['required'],
- $value['array'],
- $value['default']
- );
- case 'ip':
- return new IP(
- $value['key'],
- $collection,
- $value['required'],
- $value['array'],
- $value['default']
- );
- case 'datetime':
- return new DateTime(
- $value['key'],
- $collection,
- $value['required'],
- $value['array'],
- $value['default']
- );
- default:
- return new Text(
- $value['key'],
- $collection,
- $value['required'],
- $value['array'],
- $value['default'],
- $value['size'] ?? 0
- );
- }
+ return match ($value['format']) {
+ 'email' => new Email(
+ $value['key'],
+ $collection,
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ size: $value['size'] ?? 254,
+ ),
+ 'enum' => new Enum(
+ $value['key'],
+ $collection,
+ elements: $value['elements'],
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ size: $value['size'] ?? UtopiaDatabase::LENGTH_KEY,
+ ),
+ 'url' => new URL(
+ $value['key'],
+ $collection,
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ size: $value['size'] ?? 2000,
+ ),
+ 'ip' => new IP(
+ $value['key'],
+ $collection,
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ size: $value['size'] ?? 39,
+ ),
+ default => new Text(
+ $value['key'],
+ $collection,
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ size: $value['size'] ?? 0,
+ ),
+ };
case 'boolean':
return new Boolean(
$value['key'],
$collection,
- $value['required'],
- $value['array'],
- $value['default']
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array']
);
case 'integer':
return new Integer(
$value['key'],
$collection,
- $value['required'],
- $value['array'],
- $value['default'],
- $value['min'] ?? 0,
- $value['max'] ?? 0
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ min: $value['min'] ?? null,
+ max: $value['max'] ?? null,
);
case 'double':
return new Decimal(
$value['key'],
$collection,
- $value['required'],
- $value['array'],
- $value['default'],
- $value['min'] ?? 0,
- $value['max'] ?? 0
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
+ min: $value['min'] ?? null,
+ max: $value['max'] ?? null,
);
case 'relationship':
return new Relationship(
$value['key'],
$collection,
- $value['required'],
- $value['array'],
- $value['relatedCollection'],
- $value['relationType'],
- $value['twoWay'],
- $value['twoWayKey'],
- $value['onDelete'],
- $value['side']
+ relatedCollection: $value['relatedCollection'],
+ relationType: $value['relationType'],
+ twoWay: $value['twoWay'],
+ twoWayKey: $value['twoWayKey'],
+ onDelete: $value['onDelete'],
+ side: $value['side'],
);
case 'datetime':
return new DateTime(
$value['key'],
$collection,
- $value['required'],
- $value['array'],
- $value['default']
+ required: $value['required'],
+ default: $value['default'],
+ array: $value['array'],
);
}
- throw new \Exception('Unknown attribute type: '.$value['type']);
+ throw new \Exception('Unknown attribute type: ' . $value['type']);
}
- private function exportDatabases(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportDatabases(int $batchSize): void
{
- $databaseClient = new Databases($this->client);
+ $this->database = new Databases($this->client);
$lastDatabase = null;
- // Transfer Databases
while (true) {
$queries = [Query::limit($batchSize)];
+
+ if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_DATABASE) {
+ $queries[] = Query::equal('$id', $this->rootResourceId);
+ $queries[] = Query::limit(1);
+ }
+
$databases = [];
if ($lastDatabase) {
$queries[] = Query::cursorAfter($lastDatabase);
}
- $response = $databaseClient->list($queries);
+ $response = $this->database->list($queries);
foreach ($response['databases'] as $database) {
$newDatabase = new Database(
+ $database['$id'],
$database['name'],
- $database['$id']
);
$databases[] = $newDatabase;
@@ -807,13 +903,13 @@ private function exportDatabases(int $batchSize)
}
}
- private function exportCollections(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportCollections(int $batchSize): void
{
- $databaseClient = new Databases($this->client);
-
- // Transfer Collections
-
$databases = $this->cache->get(Database::getName());
+
foreach ($databases as $database) {
$lastCollection = null;
@@ -826,7 +922,7 @@ private function exportCollections(int $batchSize)
$queries[] = Query::cursorAfter($lastCollection);
}
- $response = $databaseClient->listCollections(
+ $response = $this->database->listCollections(
$database->getId(),
$queries
);
@@ -843,7 +939,9 @@ private function exportCollections(int $batchSize)
$collections[] = $newCollection;
}
- $lastCollection = $collections[count($collections) - 1]->getId();
+ $lastCollection = !empty($collection)
+ ? $collections[count($collections) - 1]->getId()
+ : null;
$this->callback($collections);
@@ -854,11 +952,12 @@ private function exportCollections(int $batchSize)
}
}
- private function exportAttributes(int $batchSize)
+ /**
+ * @throws AppwriteException
+ * @throws \Exception
+ */
+ private function exportAttributes(int $batchSize): void
{
- $databaseClient = new Databases($this->client);
-
- // Transfer Attributes
$collections = $this->cache->get(Collection::getName());
/** @var Collection[] $collections */
foreach ($collections as $collection) {
@@ -872,31 +971,17 @@ private function exportAttributes(int $batchSize)
$queries[] = Query::cursorAfter($lastAttribute);
}
- $response = $databaseClient->listAttributes(
+ $response = $this->database->listAttributes(
$collection->getDatabase()->getId(),
$collection->getId(),
$queries
);
- // Remove two way relationship attributes
- $this->cache->get(Resource::TYPE_ATTRIBUTE);
-
- $knownTwoways = [];
-
- foreach ($this->cache->get(Resource::TYPE_ATTRIBUTE) as $attribute) {
- /** @var Attribute|Relationship $attribute */
- if ($attribute->getTypeName() == Attribute::TYPE_RELATIONSHIP && $attribute->getTwoWay()) {
- $knownTwoways[] = $attribute->getTwoWayKey();
- }
- }
-
foreach ($response['attributes'] as $attribute) {
- if (in_array($attribute['key'], $knownTwoways)) {
- continue;
- }
+ /** @var array $attribute */
- if ($attribute['type'] === 'relationship') {
- $knownTwoways[] = $attribute['twoWayKey'];
+ if ($attribute['type'] === 'relationship' && $attribute['side'] === 'child') {
+ continue;
}
$attributes[] = $this->convertAttribute($attribute, $collection);
@@ -916,10 +1001,11 @@ private function exportAttributes(int $batchSize)
}
}
- private function exportIndexes(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportIndexes(int $batchSize): void
{
- $databaseClient = new Databases($this->client);
-
$collections = $this->cache->get(Resource::TYPE_COLLECTION);
// Transfer Indexes
@@ -935,7 +1021,7 @@ private function exportIndexes(int $batchSize)
$queries[] = Query::cursorAfter($lastIndex);
}
- $response = $databaseClient->listIndexes(
+ $response = $this->database->listIndexes(
$collection->getDatabase()->getId(),
$collection->getId(),
$queries
@@ -948,6 +1034,7 @@ private function exportIndexes(int $batchSize)
$collection,
$index['type'],
$index['attributes'],
+ [],
$index['orders']
);
}
@@ -966,26 +1053,26 @@ private function exportIndexes(int $batchSize)
}
}
- protected function exportGroupStorage(int $batchSize, array $resources)
+ protected function exportGroupStorage(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
- $this->exportBuckets($batchSize, false);
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
+ $this->exportBuckets($batchSize);
}
} catch (\Throwable $e) {
$this->addError(
new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$this->exportFiles($batchSize);
}
} catch (\Throwable $e) {
@@ -993,9 +1080,9 @@ protected function exportGroupStorage(int $batchSize, array $resources)
new Exception(
Resource::TYPE_FILE,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
@@ -1009,19 +1096,27 @@ protected function exportGroupStorage(int $batchSize, array $resources)
new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
}
- private function exportBuckets(int $batchSize, bool $updateLimits)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportBuckets(int $batchSize): void
{
- $storageClient = new Storage($this->client);
+ $queries = [];
- $buckets = $storageClient->listBuckets();
+ if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_BUCKET) {
+ $queries[] = Query::equal('$id', $this->rootResourceId);
+ $queries[] = Query::limit(1);
+ }
+
+ $buckets = $this->storage->listBuckets($queries);
$convertedBuckets = [];
@@ -1037,10 +1132,7 @@ private function exportBuckets(int $batchSize, bool $updateLimits)
$bucket['compression'],
$bucket['encryption'],
$bucket['antivirus'],
- $updateLimits
);
-
- $bucket->setUpdateLimits($updateLimits);
$convertedBuckets[] = $bucket;
}
@@ -1051,11 +1143,13 @@ private function exportBuckets(int $batchSize, bool $updateLimits)
$this->callback($convertedBuckets);
}
- private function exportFiles(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportFiles(int $batchSize): void
{
- $storageClient = new Storage($this->client);
-
$buckets = $this->cache->get(Bucket::getName());
+
foreach ($buckets as $bucket) {
/** @var Bucket $bucket */
$lastDocument = null;
@@ -1067,7 +1161,7 @@ private function exportFiles(int $batchSize)
$queries[] = Query::cursorAfter($lastDocument);
}
- $response = $storageClient->listFiles(
+ $response = $this->storage->listFiles(
$bucket->getId(),
$queries
);
@@ -1087,9 +1181,9 @@ private function exportFiles(int $batchSize)
$this->addError(new Exception(
resourceName: Resource::TYPE_FILE,
resourceGroup: Transfer::GROUP_STORAGE,
+ resourceId: $file['$id'],
message: $e->getMessage(),
- code: $e->getCode(),
- resourceId: $file['$id']
+ code: $e->getCode()
));
}
@@ -1103,7 +1197,10 @@ private function exportFiles(int $batchSize)
}
}
- private function exportFileData(File $file)
+ /**
+ * @throws \Exception
+ */
+ private function exportFileData(File $file): void
{
// Set the chunk size (5MB)
$start = 0;
@@ -1125,7 +1222,8 @@ private function exportFileData(File $file)
);
// Send the chunk to the callback function
- $file->setData($chunkData)
+ $file
+ ->setData($chunkData)
->setStart($start)
->setEnd($end);
@@ -1141,42 +1239,52 @@ private function exportFileData(File $file)
}
}
- protected function exportGroupFunctions(int $batchSize, array $resources)
+ protected function exportGroupFunctions(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_FUNCTION, $resources)) {
+ if (\in_array(Resource::TYPE_FUNCTION, $resources)) {
$this->exportFunctions($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_FUNCTION,
Transfer::GROUP_FUNCTIONS,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
try {
- if (in_array(Resource::TYPE_DEPLOYMENT, $resources)) {
+ if (\in_array(Resource::TYPE_DEPLOYMENT, $resources)) {
$this->exportDeployments($batchSize, true);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_DEPLOYMENT,
Transfer::GROUP_FUNCTIONS,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
}
- private function exportFunctions(int $batchSize)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportFunctions(int $batchSize): void
{
- $functionsClient = new Functions($this->client);
+ $this->functions = new Functions($this->client);
+
+ $queries = [];
+
+ if ($this->rootResourceId !== '' && $this->rootResourceType === Resource::TYPE_FUNCTION) {
+ $queries[] = Query::equal('$id', $this->rootResourceId);
+ $queries[] = Query::limit(1);
+ }
- $functions = $functionsClient->list();
+ $functions = $this->functions->list($queries);
if ($functions['total'] === 0) {
return;
@@ -1186,21 +1294,23 @@ private function exportFunctions(int $batchSize)
foreach ($functions['functions'] as $function) {
$convertedFunc = new Func(
- $function['name'],
$function['$id'],
+ $function['name'],
$function['runtime'],
$function['execute'],
$function['enabled'],
$function['events'],
$function['schedule'],
$function['timeout'],
- $function['deployment']
+ $function['deployment'],
+ $function['entrypoint']
);
$convertedResources[] = $convertedFunc;
foreach ($function['vars'] as $var) {
$convertedResources[] = new EnvVar(
+ $var['$id'],
$convertedFunc,
$var['key'],
$var['value'],
@@ -1211,24 +1321,20 @@ private function exportFunctions(int $batchSize)
$this->callback($convertedResources);
}
- private function exportDeployments(int $batchSize, bool $exportOnlyActive = false)
+ /**
+ * @throws AppwriteException
+ */
+ private function exportDeployments(int $batchSize, bool $exportOnlyActive = false): void
{
- $functionsClient = new Functions($this->client);
+ $this->functions = new Functions($this->client);
$functions = $this->cache->get(Func::getName());
- // exportDeploymentData doesn't exist on Appwrite versions prior to 1.4
- $appwriteVersion = $this->call('GET', '/health/version', ['X-Appwrite-Key' => '', 'X-Appwrite-Project' => ''])['version'];
-
- if (version_compare($appwriteVersion, '1.4.0', '<')) {
- return;
- }
-
foreach ($functions as $func) {
/** @var Func $func */
$lastDocument = null;
if ($exportOnlyActive && $func->getActiveDeployment()) {
- $deployment = $functionsClient->getDeployment($func->getId(), $func->getActiveDeployment());
+ $deployment = $this->functions->getDeployment($func->getId(), $func->getActiveDeployment());
try {
$this->exportDeploymentData($func, $deployment);
@@ -1246,7 +1352,7 @@ private function exportDeployments(int $batchSize, bool $exportOnlyActive = fals
$queries[] = Query::cursorAfter($lastDocument);
}
- $response = $functionsClient->listDeployments(
+ $response = $this->functions->listDeployments(
$func->getId(),
$queries
);
@@ -1268,7 +1374,10 @@ private function exportDeployments(int $batchSize, bool $exportOnlyActive = fals
}
}
- private function exportDeploymentData(Func $func, array $deployment)
+ /**
+ * @throws \Exception
+ */
+ private function exportDeploymentData(Func $func, array $deployment): void
{
// Set the chunk size (5MB)
$start = 0;
@@ -1286,7 +1395,7 @@ private function exportDeploymentData(Func $func, array $deployment)
);
// Content-Length header was missing, file is less than max buffer size.
- if (! array_key_exists('Content-Length', $responseHeaders)) {
+ if (!array_key_exists('Content-Length', $responseHeaders)) {
$file = $this->call(
'GET',
"/functions/{$func->getId()}/deployments/{$deployment['$id']}/download",
@@ -1295,10 +1404,16 @@ private function exportDeploymentData(Func $func, array $deployment)
$responseHeaders
);
+ $size = mb_strlen($file);
+
+ if ($end > $size) {
+ $end = $size - 1;
+ }
+
$deployment = new Deployment(
$deployment['$id'],
$func,
- strlen($file),
+ $size,
$deployment['entrypoint'],
$start,
$end,
@@ -1307,7 +1422,8 @@ private function exportDeploymentData(Func $func, array $deployment)
);
$deployment->setInternalId($deployment->getId());
- return $this->callback([$deployment]);
+ $this->callback([$deployment]);
+ return;
}
$fileSize = $responseHeaders['Content-Length'];
@@ -1334,9 +1450,11 @@ private function exportDeploymentData(Func $func, array $deployment)
);
// Send the chunk to the callback function
- $deployment->setData($chunkData);
- $deployment->setStart($start);
- $deployment->setEnd($end);
+ $deployment
+ ->setData($chunkData)
+ ->setStart($start)
+ ->setEnd($end);
+
$this->callback([$deployment]);
// Update the range
@@ -1348,4 +1466,9 @@ private function exportDeploymentData(Func $func, array $deployment)
}
}
}
+
+ public function getBatchSize(): int
+ {
+ return 250;
+ }
}
diff --git a/src/Migration/Sources/Firebase.php b/src/Migration/Sources/Firebase.php
index 6f5ad79..015ae73 100644
--- a/src/Migration/Sources/Firebase.php
+++ b/src/Migration/Sources/Firebase.php
@@ -22,6 +22,9 @@
class Firebase extends Source
{
+ /**
+ * @var array
+ */
private array $serviceAccount;
private string $projectID;
@@ -30,6 +33,9 @@ class Firebase extends Source
private int $tokenExpires = 0;
+ /**
+ * @param array $serviceAccount
+ */
public function __construct(array $serviceAccount)
{
$this->serviceAccount = $serviceAccount;
@@ -41,7 +47,7 @@ public static function getName(): string
return 'Firebase';
}
- private function base64UrlEncode($data)
+ private function base64UrlEncode(string $data): string
{
return str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($data));
}
@@ -51,8 +57,8 @@ private function calculateJWT(): string
$jwtClaim = [
'iss' => $this->serviceAccount['client_email'],
'scope' => 'https://www.googleapis.com/auth/firebase https://www.googleapis.com/auth/cloud-platform https://www.googleapis.com/auth/datastore',
- 'exp' => time() + 3600,
- 'iat' => time(),
+ 'exp' => \time() + 3600,
+ 'iat' => \time(),
'aud' => 'https://oauth2.googleapis.com/token',
];
@@ -64,16 +70,23 @@ private function calculateJWT(): string
$jwtPayload = $this->base64UrlEncode(json_encode($jwtHeader)).'.'.$this->base64UrlEncode(json_encode($jwtClaim));
$jwtSignature = '';
- openssl_sign($jwtPayload, $jwtSignature, $this->serviceAccount['private_key'], 'sha256');
+
+ \openssl_sign(
+ $jwtPayload,
+ $jwtSignature,
+ $this->serviceAccount['private_key'],
+ 'sha256'
+ );
+
$jwtSignature = $this->base64UrlEncode($jwtSignature);
- return $jwtPayload.'.'.$jwtSignature;
+ return $jwtPayload . '.' . $jwtSignature;
}
/**
* Computes the JWT then fetches an auth token from the Google OAuth2 API which is valid for an hour
*/
- private function authenticate()
+ private function authenticate(): void
{
if (time() < $this->tokenExpires) {
return;
@@ -95,7 +108,7 @@ private function authenticate()
}
}
- protected function call(string $method, string $path = '', array $headers = [], array $params = [], &$responseHeaders = []): array|string
+ protected function call(string $method, string $path = '', array $headers = [], array $params = [], array &$responseHeaders = []): array|string
{
$this->authenticate();
@@ -148,7 +161,7 @@ public function report(array $resources = []): array
return [];
}
- protected function exportGroupAuth(int $batchSize, array $resources)
+ protected function exportGroupAuth(int $batchSize, array $resources): void
{
// Check if Auth is enabled
try {
@@ -165,7 +178,7 @@ protected function exportGroupAuth(int $batchSize, array $resources)
}
try {
- if (in_array(Resource::TYPE_USER, $resources)) {
+ if (\in_array(Resource::TYPE_USER, $resources)) {
$this->exportUsers($batchSize);
}
} catch (\Throwable $e) {
@@ -173,15 +186,15 @@ protected function exportGroupAuth(int $batchSize, array $resources)
new Exception(
Resource::TYPE_USER,
Transfer::GROUP_AUTH,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
}
- private function exportUsers(int $batchSize)
+ private function exportUsers(int $batchSize): void
{
// Fetch our Hash Config
$hashConfig = ($this->call('GET', 'https://identitytoolkit.googleapis.com/admin/v2/projects/'.$this->projectID.'/config'))['signIn']['hashConfig'];
@@ -213,11 +226,17 @@ private function exportUsers(int $batchSize)
$nextPageToken = $response['nextPageToken'] ?? null;
foreach ($result as $user) {
+ $hash = null;
+
+ if (array_key_exists('passwordHash', $user)) {
+ $hash = new Hash($user['passwordHash'], $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'] ?? '', $hashConfig['signerKey'] ?? '');
+ }
+
$transferUser = new User(
$user['localId'] ?? '',
$user['email'] ?? null,
$user['displayName'] ?? $user['email'] ?? null,
- null,
+ $hash,
$user['phoneNumber'] ?? null,
[],
'',
@@ -226,12 +245,6 @@ private function exportUsers(int $batchSize)
$user['disabled'] ?? false
);
- if (array_key_exists('passwordHash', $user)) {
- $transferUser->setPasswordHash(
- new Hash($user['passwordHash'], $user['salt'] ?? '', Hash::ALGORITHM_SCRYPT_MODIFIED, $hashConfig['saltSeparator'] ?? '', $hashConfig['signerKey'] ?? '')
- );
- }
-
$users[] = $transferUser;
}
@@ -243,7 +256,7 @@ private function exportUsers(int $batchSize)
}
}
- protected function exportGroupDatabases(int $batchSize, array $resources)
+ protected function exportGroupDatabases(int $batchSize, array $resources): void
{
// Check if Firestore is enabled
try {
@@ -260,7 +273,7 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
}
try {
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
+ if (\in_array(Resource::TYPE_DATABASE, $resources)) {
$database = new Database('default', 'default');
$database->setOriginalId('(default)');
$this->callback([$database]);
@@ -270,15 +283,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_DATABASE,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
+ if (\in_array(Resource::TYPE_COLLECTION, $resources)) {
$this->exportDB($batchSize, in_array(Resource::TYPE_DOCUMENT, $resources), $database);
}
} catch (\Throwable $e) {
@@ -286,15 +299,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_COLLECTION,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
}
- private function exportDB(int $batchSize, bool $pushDocuments, Database $database)
+ private function exportDB(int $batchSize, bool $pushDocuments, Database $database): void
{
$baseURL = "https://firestore.googleapis.com/v1/projects/{$this->projectID}/databases/(default)/documents";
@@ -352,28 +365,97 @@ private function exportDB(int $batchSize, bool $pushDocuments, Database $databas
}
}
+ /**
+ * @throws \Exception
+ */
private function convertAttribute(Collection $collection, string $key, array $field): Attribute
{
if (array_key_exists('booleanValue', $field)) {
- return new Boolean($key, $collection, false, false, null);
+ return new Boolean(
+ $key,
+ $collection,
+ required:false,
+ default: null,
+ array: false,
+ );
} elseif (array_key_exists('bytesValue', $field)) {
- return new Text($key, $collection, false, false, null, 1000000);
+ return new Text(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ size: 1000000,
+ );
} elseif (array_key_exists('doubleValue', $field)) {
- return new Decimal($key, $collection, false, false, null);
+ return new Decimal(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ );
} elseif (array_key_exists('integerValue', $field)) {
- return new Integer($key, $collection, false, false, null);
+ return new Integer(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ );
} elseif (array_key_exists('mapValue', $field)) {
- return new Text($key, $collection, false, false, null, 1000000);
+ return new Text(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ size: 1000000,
+ );
} elseif (array_key_exists('nullValue', $field)) {
- return new Text($key, $collection, false, false, null, 1000000);
+ return new Text(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ size: 1000000,
+ );
} elseif (array_key_exists('referenceValue', $field)) {
- return new Text($key, $collection, false, false, null, 1000000); //TODO: This should be a reference attribute
+ return new Text(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ size: 1000000,
+ ); //TODO: This should be a reference attribute
} elseif (array_key_exists('stringValue', $field)) {
- return new Text($key, $collection, false, false, null, 1000000);
+ return new Text(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ size: 1000000,
+ );
} elseif (array_key_exists('timestampValue', $field)) {
- return new DateTime($key, $collection, false, false, null);
+ return new DateTime(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ );
} elseif (array_key_exists('geoPointValue', $field)) {
- return new Text($key, $collection, false, false, null, 1000000);
+ return new Text(
+ $key,
+ $collection,
+ required: false,
+ default: null,
+ array: false,
+ size: 1000000,
+ );
} elseif (array_key_exists('arrayValue', $field)) {
return $this->calculateArrayType($collection, $key, $field['arrayValue']);
} else {
@@ -404,7 +486,7 @@ private function calculateArrayType(Collection $collection, string $key, array $
}
}
- private function exportCollection(Collection $collection, int $batchSize, bool $transferDocuments)
+ private function exportCollection(Collection $collection, int $batchSize, bool $transferDocuments): void
{
$resourceURL = 'https://firestore.googleapis.com/v1/projects/'.$this->projectID.'/databases/'.$collection->getDatabase()->getOriginalId().'/documents/'.$collection->getId();
@@ -516,10 +598,10 @@ private function convertDocument(Collection $collection, array $document): Docum
$documentId = preg_replace("/[^A-Za-z0-9\_\-]/", '', $documentId);
$documentId = strtolower($documentId);
- return new Document($documentId, $collection->getDatabase(), $collection, $data, []);
+ return new Document($documentId, $collection, $data, []);
}
- protected function exportGroupStorage(int $batchSize, array $resources)
+ protected function exportGroupStorage(int $batchSize, array $resources): void
{
// Check if storage is enabled
try {
@@ -540,36 +622,36 @@ protected function exportGroupStorage(int $batchSize, array $resources)
}
try {
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
$this->exportBuckets($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
try {
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$this->exportFiles($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_FILE,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
}
- private function exportBuckets(int $batchsize)
+ private function exportBuckets(int $batchsize): void
{
$endpoint = 'https://storage.googleapis.com/storage/v1/b';
@@ -609,7 +691,7 @@ private function exportBuckets(int $batchsize)
}
}
- private function sanitizeBucketId($id)
+ private function sanitizeBucketId($id): array|string|null
{
// Step 1: Check if the ID looks like a URL (contains ".")
if (strpos($id, '.') !== false) {
@@ -634,7 +716,7 @@ private function sanitizeBucketId($id)
return $id;
}
- private function exportFiles(int $batchsize)
+ private function exportFiles(int $batchsize): void
{
$buckets = $this->cache->get(Bucket::getName());
@@ -673,7 +755,7 @@ private function exportFiles(int $batchsize)
}
}
- private function exportFile(File $file)
+ private function exportFile(File $file): void
{
$endpoint = 'https://storage.googleapis.com/storage/v1/b/'.$file->getBucket()->getOriginalId().'/o/'.$file->getId().'?alt=media';
$start = 0;
@@ -703,7 +785,7 @@ private function exportFile(File $file)
}
}
- protected function exportGroupFunctions(int $batchSize, array $resources)
+ protected function exportGroupFunctions(int $batchSize, array $resources): void
{
throw new \Exception('Not implemented');
}
diff --git a/src/Migration/Sources/NHost.php b/src/Migration/Sources/NHost.php
index b8306df..b6e24ae 100644
--- a/src/Migration/Sources/NHost.php
+++ b/src/Migration/Sources/NHost.php
@@ -114,7 +114,7 @@ public function report(array $resources = []): array
}
// Auth
- if (in_array(Resource::TYPE_USER, $resources)) {
+ if (\in_array(Resource::TYPE_USER, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM auth.users');
$statement->execute();
@@ -126,11 +126,11 @@ public function report(array $resources = []): array
}
// Databases
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
+ if (\in_array(Resource::TYPE_DATABASE, $resources)) {
$report[Resource::TYPE_DATABASE] = 1;
}
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
+ if (\in_array(Resource::TYPE_COLLECTION, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'');
$statement->execute();
@@ -141,7 +141,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_COLLECTION] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
+ if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\'');
$statement->execute();
@@ -152,7 +152,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_INDEX, $resources)) {
+ if (\in_array(Resource::TYPE_INDEX, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\'');
$statement->execute();
@@ -163,7 +163,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_INDEX] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_DOCUMENT, $resources)) {
+ if (\in_array(Resource::TYPE_DOCUMENT, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'');
$statement->execute();
@@ -175,7 +175,7 @@ public function report(array $resources = []): array
}
// Storage
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM storage.buckets');
$statement->execute();
@@ -186,7 +186,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_BUCKET] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$statement = $db->prepare('SELECT COUNT(*) FROM storage.files');
$statement->execute();
@@ -211,24 +211,24 @@ public function report(array $resources = []): array
return $report;
}
- protected function exportGroupAuth(int $batchSize, array $resources)
+ protected function exportGroupAuth(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_USER, $resources)) {
+ if (\in_array(Resource::TYPE_USER, $resources)) {
$this->exportUsers($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_USER,
Transfer::GROUP_AUTH,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
}
- private function exportUsers(int $batchSize)
+ private function exportUsers(int $batchSize): void
{
$db = $this->getDatabase();
@@ -249,11 +249,17 @@ private function exportUsers(int $batchSize)
$transferUsers = [];
foreach ($users as $user) {
+ $hash = null;
+
+ if (array_key_exists('password_hash', $user)) {
+ $hash = new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT);
+ }
+
$transferUser = new User(
$user['id'],
$user['email'] ?? null,
$user['display_name'] ?? null,
- null,
+ $hash,
$user['phone_number'] ?? null,
[],
'',
@@ -263,10 +269,6 @@ private function exportUsers(int $batchSize)
[]
);
- if (array_key_exists('password_hash', $user)) {
- $transferUser->setPasswordHash(new Hash($user['password_hash'], '', Hash::ALGORITHM_BCRYPT));
- }
-
$transferUsers[] = $transferUser;
}
@@ -274,10 +276,10 @@ private function exportUsers(int $batchSize)
}
}
- protected function exportGroupDatabases(int $batchSize, array $resources)
+ protected function exportGroupDatabases(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
+ if (\in_array(Resource::TYPE_DATABASE, $resources)) {
$this->exportDatabases($batchSize);
}
} catch (\Throwable $e) {
@@ -285,15 +287,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_DATABASE,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
+ if (\in_array(Resource::TYPE_COLLECTION, $resources)) {
$this->exportCollections($batchSize);
}
} catch (\Throwable $e) {
@@ -301,15 +303,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_COLLECTION,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
+ if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
$this->exportAttributes($batchSize);
}
} catch (\Throwable $e) {
@@ -317,15 +319,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_ATTRIBUTE,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_DOCUMENT, $resources)) {
+ if (\in_array(Resource::TYPE_DOCUMENT, $resources)) {
$this->exportDocuments($batchSize);
}
} catch (\Throwable $e) {
@@ -333,15 +335,15 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_DOCUMENT,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_INDEX, $resources)) {
+ if (\in_array(Resource::TYPE_INDEX, $resources)) {
$this->exportIndexes($batchSize);
}
} catch (\Throwable $e) {
@@ -349,9 +351,9 @@ protected function exportGroupDatabases(int $batchSize, array $resources)
new Exception(
Resource::TYPE_INDEX,
Transfer::GROUP_DATABASES,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
@@ -365,7 +367,7 @@ private function exportDatabases(int $batchSize): void
$this->callback([$transferDatabase]);
}
- private function exportCollections(int $batchSize)
+ private function exportCollections(int $batchSize): void
{
$databases = $this->cache->get(Database::getName());
$db = $this->getDatabase();
@@ -397,7 +399,7 @@ private function exportCollections(int $batchSize)
}
}
- private function exportAttributes(int $batchSize)
+ private function exportAttributes(int $batchSize): void
{
$collections = $this->cache->get(Collection::getName());
$db = $this->getDatabase();
@@ -419,7 +421,7 @@ private function exportAttributes(int $batchSize)
}
}
- private function exportIndexes(int $batchSize)
+ private function exportIndexes(int $batchSize): void
{
$collections = $this->cache->get(Collection::getName());
$db = $this->getDatabase();
@@ -444,7 +446,7 @@ private function exportIndexes(int $batchSize)
}
}
- private function exportDocuments(int $batchSize)
+ private function exportDocuments(int $batchSize): void
{
$databases = $this->cache->get(Database::getName());
$collections = $this->cache->get(Collection::getName());
@@ -458,12 +460,12 @@ private function exportDocuments(int $batchSize)
foreach ($collections as $collection) {
/** @var Collection $collection */
- $total = $db->query('SELECT COUNT(*) FROM '.$collection->getDatabase()->getDBName().'."'.$collection->getCollectionName().'"')->fetchColumn();
+ $total = $db->query('SELECT COUNT(*) FROM '.$collection->getDatabase()->getDatabaseName().'."'.$collection->getCollectionName().'"')->fetchColumn();
$offset = 0;
while ($offset < $total) {
- $statement = $db->prepare('SELECT row_to_json(t) FROM (SELECT * FROM '.$collection->getDatabase()->getDBName().'."'.$collection->getCollectionName().'" LIMIT :limit OFFSET :offset) t;');
+ $statement = $db->prepare('SELECT row_to_json(t) FROM (SELECT * FROM '.$collection->getDatabase()->getDatabaseName().'."'.$collection->getCollectionName().'" LIMIT :limit OFFSET :offset) t;');
$statement->bindValue(':limit', $batchSize, \PDO::PARAM_INT);
$statement->bindValue(':offset', $offset, \PDO::PARAM_INT);
$statement->execute();
@@ -485,14 +487,14 @@ private function exportDocuments(int $batchSize)
$processedData = [];
foreach ($collectionAttributes as $attribute) {
/** @var Attribute $attribute */
- if (! $attribute->getArray() && \is_array($data[$attribute->getKey()])) {
+ if (! $attribute->isArray() && \is_array($data[$attribute->getKey()])) {
$processedData[$attribute->getKey()] = json_encode($data[$attribute->getKey()]);
} else {
$processedData[$attribute->getKey()] = $data[$attribute->getKey()];
}
}
- $transferDocuments[] = new Document('unique()', $database, $collection, $processedData);
+ $transferDocuments[] = new Document('unique()', $collection, $processedData);
}
$this->callback($transferDocuments);
@@ -509,7 +511,13 @@ private function convertAttribute(array $column, Collection $collection): Attrib
// Numbers
case 'boolean':
case 'bool':
- return new Boolean($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']);
+ return new Boolean(
+ $column['column_name'],
+ $collection,
+ required: $column['is_nullable'] === 'NO',
+ default: $column['column_default'],
+ array: $isArray,
+ );
case 'smallint':
case 'int2':
if (! is_numeric($column['column_default']) && ! is_null($column['column_default'])) {
@@ -525,7 +533,15 @@ private function convertAttribute(array $column, Collection $collection): Attrib
$column['column_default'] = null;
}
- return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -32768, 32767);
+ return new Integer(
+ $column['column_name'],
+ $collection,
+ required: $column['is_nullable'] === 'NO',
+ default:$column['column_default'],
+ array: $isArray,
+ min: -32768,
+ max: 32767,
+ );
case 'integer':
case 'int4':
if (! is_numeric($column['column_default']) && ! is_null($column['column_default'])) {
@@ -541,7 +557,15 @@ private function convertAttribute(array $column, Collection $collection): Attrib
$column['column_default'] = null;
}
- return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default'], -2147483648, 2147483647);
+ return new Integer(
+ $column['column_name'],
+ $collection,
+ required: $column['is_nullable'] === 'NO',
+ default: $column['column_default'],
+ array: $isArray,
+ min: -2147483648,
+ max: 2147483647,
+ );
case 'bigint':
case 'int8':
case 'numeric':
@@ -557,7 +581,13 @@ private function convertAttribute(array $column, Collection $collection): Attrib
$column['column_default'] = null;
}
- return new Integer($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']);
+ return new Integer(
+ $column['column_name'],
+ $collection,
+ required: $column['is_nullable'] === 'NO',
+ default: $column['column_default'],
+ array: $isArray,
+ );
case 'decimal':
case 'real':
case 'double precision':
@@ -577,7 +607,13 @@ private function convertAttribute(array $column, Collection $collection): Attrib
$column['column_default'] = null;
}
- return new Decimal($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, $column['column_default']);
+ return new Decimal(
+ $column['column_name'],
+ $collection,
+ required: $column['is_nullable'] === 'NO',
+ default: $column['column_default'],
+ array: $isArray,
+ );
// Time (Conversion happens with documents)
case 'timestamp with time zone':
case 'date':
@@ -588,36 +624,23 @@ private function convertAttribute(array $column, Collection $collection): Attrib
case 'time':
case 'timetz':
case 'interval':
- return new DateTime($column['column_name'], $collection, $column['is_nullable'] === 'NO', $isArray, null);
- break;
- // Strings and Objects
- case 'uuid':
- case 'character varying':
- case 'text':
- case 'character':
- case 'json':
- case 'jsonb':
- case 'varchar':
- case 'bytea':
- return new Text(
+ return new DateTime(
$column['column_name'],
$collection,
- $column['is_nullable'] === 'NO',
- $isArray,
- $column['column_default'],
- $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760
+ required: $column['is_nullable'] === 'NO',
+ default: null,
+ array: $isArray,
);
- break;
default:
+ // Strings and Objects
return new Text(
$column['column_name'],
$collection,
- $column['is_nullable'] === 'NO',
- $isArray,
- $column['column_default'],
- $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760
+ required: $column['is_nullable'] === 'NO',
+ default: $column['column_default'],
+ array: $isArray,
+ size: $column['character_maximum_length'] ?? $column['character_octet_length'] ?? 10485760,
);
- break;
}
}
@@ -665,10 +688,10 @@ private function convertIndex(array $index, Collection $collection): Index|false
}
}
- protected function exportGroupStorage(int $batchSize, array $resources)
+ protected function exportGroupStorage(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
$this->exportBuckets($batchSize);
}
} catch (\Throwable $e) {
@@ -676,15 +699,15 @@ protected function exportGroupStorage(int $batchSize, array $resources)
new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
try {
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$this->exportFiles($batchSize);
}
} catch (\Throwable $e) {
@@ -692,15 +715,15 @@ protected function exportGroupStorage(int $batchSize, array $resources)
new Exception(
Resource::TYPE_FILE,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
)
);
}
}
- protected function exportBuckets(int $batchSize)
+ protected function exportBuckets(int $batchSize): void
{
$db = $this->getDatabase();
$total = $db->query('SELECT COUNT(*) FROM storage.buckets')->fetchColumn();
@@ -730,7 +753,7 @@ protected function exportBuckets(int $batchSize)
}
}
- private function exportFiles(int $batchSize)
+ private function exportFiles(int $batchSize): void
{
$buckets = $this->cache->get(Bucket::getName());
$db = $this->getDatabase();
@@ -768,7 +791,7 @@ private function exportFiles(int $batchSize)
}
}
- private function exportFile(File $file)
+ private function exportFile(File $file): void
{
$start = 0;
$end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1;
@@ -816,7 +839,7 @@ private function exportFile(File $file)
}
}
- protected function exportGroupFunctions(int $batchSize, array $resources)
+ protected function exportGroupFunctions(int $batchSize, array $resources): void
{
throw new \Exception('Not Implemented');
}
diff --git a/src/Migration/Sources/Supabase.php b/src/Migration/Sources/Supabase.php
index 102affd..e06af13 100644
--- a/src/Migration/Sources/Supabase.php
+++ b/src/Migration/Sources/Supabase.php
@@ -248,7 +248,7 @@ public function report(array $resources = []): array
}
// Auth
- if (in_array(Resource::TYPE_USER, $resources)) {
+ if (\in_array(Resource::TYPE_USER, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM auth.users');
$statement->execute();
@@ -260,11 +260,11 @@ public function report(array $resources = []): array
}
// Databases
- if (in_array(Resource::TYPE_DATABASE, $resources)) {
+ if (\in_array(Resource::TYPE_DATABASE, $resources)) {
$report[Resource::TYPE_DATABASE] = 1;
}
- if (in_array(Resource::TYPE_COLLECTION, $resources)) {
+ if (\in_array(Resource::TYPE_COLLECTION, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'');
$statement->execute();
@@ -275,7 +275,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_COLLECTION] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
+ if (\in_array(Resource::TYPE_ATTRIBUTE, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.columns WHERE table_schema = \'public\'');
$statement->execute();
@@ -286,7 +286,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_ATTRIBUTE] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_INDEX, $resources)) {
+ if (\in_array(Resource::TYPE_INDEX, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM pg_indexes WHERE schemaname = \'public\'');
$statement->execute();
@@ -297,7 +297,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_INDEX] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_DOCUMENT, $resources)) {
+ if (\in_array(Resource::TYPE_DOCUMENT, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = \'public\'');
$statement->execute();
@@ -309,7 +309,7 @@ public function report(array $resources = []): array
}
// Storage
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.buckets');
$statement->execute();
@@ -320,7 +320,7 @@ public function report(array $resources = []): array
$report[Resource::TYPE_BUCKET] = $statement->fetchColumn();
}
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$statement = $this->pdo->prepare('SELECT COUNT(*) FROM storage.objects');
$statement->execute();
@@ -346,24 +346,24 @@ public function report(array $resources = []): array
return $report;
}
- protected function exportGroupAuth(int $batchSize, array $resources)
+ protected function exportGroupAuth(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_USER, $resources)) {
+ if (\in_array(Resource::TYPE_USER, $resources)) {
$this->exportUsers($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
}
- private function exportUsers(int $batchSize)
+ private function exportUsers(int $batchSize): void
{
$total = $this->pdo->query('SELECT COUNT(*) FROM auth.users')->fetchColumn();
@@ -382,11 +382,17 @@ private function exportUsers(int $batchSize)
$transferUsers = [];
foreach ($users as $user) {
+ $hash = null;
+
+ if (array_key_exists('encrypted_password', $user)) {
+ $hash = new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT);
+ }
+
$transferUser = new User(
$user['id'],
$user['email'] ?? null,
'',
- null,
+ $hash,
$user['phone'] ?? null,
[],
'',
@@ -396,10 +402,6 @@ private function exportUsers(int $batchSize)
[]
);
- if (array_key_exists('encrypted_password', $user)) {
- $transferUser->setPasswordHash(new Hash($user['encrypted_password'], '', Hash::ALGORITHM_BCRYPT));
- }
-
$transferUsers[] = $transferUser;
}
@@ -418,38 +420,38 @@ private function convertMimes(array $mimes): array
return $extensions;
}
- protected function exportGroupStorage(int $batchSize, array $resources)
+ protected function exportGroupStorage(int $batchSize, array $resources): void
{
try {
- if (in_array(Resource::TYPE_BUCKET, $resources)) {
+ if (\in_array(Resource::TYPE_BUCKET, $resources)) {
$this->exportBuckets($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
try {
- if (in_array(Resource::TYPE_FILE, $resources)) {
+ if (\in_array(Resource::TYPE_FILE, $resources)) {
$this->exportFiles($batchSize);
}
} catch (\Throwable $e) {
$this->addError(new Exception(
Resource::TYPE_BUCKET,
Transfer::GROUP_STORAGE,
- $e->getMessage(),
- $e->getCode(),
- $e
+ message: $e->getMessage(),
+ code: $e->getCode(),
+ previous: $e
));
}
}
- protected function exportBuckets(int $batchSize)
+ protected function exportBuckets(int $batchSize): void
{
$statement = $this->pdo->prepare('SELECT * FROM storage.buckets order by created_at');
$statement->execute();
@@ -475,7 +477,7 @@ protected function exportBuckets(int $batchSize)
$this->callback($transferBuckets);
}
- public function exportFiles(int $batchSize)
+ public function exportFiles(int $batchSize): void
{
/**
* TODO: Supabase has folders, with enough folders within folders this could cause us to hit the max name length
@@ -519,7 +521,7 @@ public function exportFiles(int $batchSize)
}
}
- public function exportFile(File $file)
+ public function exportFile(File $file): void
{
$start = 0;
$end = Transfer::STORAGE_MAX_CHUNK_SIZE - 1;
diff --git a/src/Migration/Target.php b/src/Migration/Target.php
index e07e5e3..664f5c1 100644
--- a/src/Migration/Target.php
+++ b/src/Migration/Target.php
@@ -9,27 +9,31 @@ abstract class Target
*
* @var array
*/
- protected $headers = [
+ protected array $headers = [
'Content-Type' => '',
];
- public $cache;
+ public Cache $cache;
/**
* Errors
*
* @var array
*/
- public $errors = [];
+ public array $errors = [];
/**
* Warnings
*
* @var array
*/
- public $warnings = [];
+ public array $warnings = [];
- protected $endpoint = '';
+ protected string $endpoint = '';
+
+ protected string $rootResourceId = '';
+
+ protected string $rootResourceType = '';
abstract public static function getName(): string;
@@ -43,10 +47,11 @@ public function registerCache(Cache &$cache): void
/**
* Run Transfer
*
- * @param string[] $resources Resources to transfer
+ * @param array $resources Resources to transfer
* @param callable $callback Callback to run after transfer
+ * @param string $rootResourceId Root resource ID, If enabled you can only transfer a single root resource
*/
- abstract public function run(array $resources, callable $callback): void;
+ abstract public function run(array $resources, callable $callback, string $rootResourceId = ''): void;
/**
* Report Resources
@@ -57,38 +62,44 @@ abstract public function run(array $resources, callable $callback): void;
* On Destinations, this function should just return nothing but still check if the API is available.
* If any issues are found then an exception should be thrown with an error message.
*
- * @param string[] $resources Resources to report
+ * @param array $resources Resources to report
+ * @return array
*/
abstract public function report(array $resources = []): array;
/**
- * Call
- *
* Make an API call
*
+ * @param array $headers
+ * @param array $params
+ * @param array $responseHeaders
+ * @return array|string
+ *
* @throws \Exception
*/
- protected function call(string $method, string $path = '', array $headers = [], array $params = [], &$responseHeaders = []): array|string
- {
- $headers = array_merge($this->headers, $headers);
- $ch = curl_init((str_contains($path, 'http') ? $path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : '') : $this->endpoint.$path.(($method == 'GET' && ! empty($params)) ? '?'.http_build_query($params) : '')));
- $responseStatus = -1;
- $responseType = '';
- $responseBody = '';
-
- switch ($headers['Content-Type']) {
- case 'application/json':
- $query = json_encode($params);
- break;
-
- case 'multipart/form-data':
- $query = $this->flatten($params);
- break;
-
- default:
- $query = http_build_query($params);
- break;
- }
+ protected function call(
+ string $method,
+ string $path = '',
+ array $headers = [],
+ array $params = [],
+ array &$responseHeaders = []
+ ): array|string {
+ $headers = \array_merge($this->headers, $headers);
+ $ch = \curl_init((
+ \str_contains($path, 'http')
+ ? $path.(($method == 'GET' && ! empty($params)) ? '?'.\http_build_query($params) : '')
+ : $this->endpoint.$path.(
+ ($method == 'GET' && ! empty($params))
+ ? '?'.\http_build_query($params)
+ : ''
+ )
+ ));
+
+ $query = match ($headers['Content-Type']) {
+ 'application/json' => \json_encode($params),
+ 'multipart/form-data' => $this->flatten($params),
+ default => \http_build_query($params),
+ };
foreach ($headers as $i => $header) {
$headers[] = $i.':'.$header;
@@ -96,51 +107,51 @@ protected function call(string $method, string $path = '', array $headers = [],
}
if ($method === 'HEAD') {
- curl_setopt($ch, CURLOPT_NOBODY, true);
+ \curl_setopt($ch, CURLOPT_NOBODY, true);
} else {
- curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
+ \curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
}
- curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
- curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s').'-'.php_uname('r').':php-'.phpversion());
- curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
- curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
- curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
+ \curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
+ \curl_setopt($ch, CURLOPT_USERAGENT, php_uname('s').'-'.php_uname('r').':php-'.phpversion());
+ \curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
+ \curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
+ \curl_setopt($ch, CURLOPT_HEADERFUNCTION, function ($curl, $header) use (&$responseHeaders) {
$len = strlen($header);
$header = explode(':', strtolower($header), 2);
- if (count($header) < 2) { // ignore invalid headers
+ if (\count($header) < 2) { // ignore invalid headers
return $len;
}
- $responseHeaders[strtolower(trim($header[0]))] = trim($header[1]);
+ $responseHeaders[\strtolower(\trim($header[0]))] = \trim($header[1]);
return $len;
});
if ($method != 'GET') {
- curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
+ \curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
}
$responseBody = curl_exec($ch);
$responseType = $responseHeaders['Content-Type'] ?? $responseHeaders['content-type'] ?? '';
- $responseStatus = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+ $responseStatus = \curl_getinfo($ch, CURLINFO_HTTP_CODE);
- switch (substr($responseType, 0, strpos($responseType, ';'))) {
+ switch (\substr($responseType, 0, \strpos($responseType, ';'))) {
case 'application/json':
- $responseBody = json_decode($responseBody, true);
+ $responseBody = \json_decode($responseBody, true);
break;
}
- if (curl_errno($ch)) {
- throw new \Exception(curl_error($ch));
+ if (\curl_errno($ch)) {
+ throw new \Exception(\curl_error($ch));
}
- curl_close($ch);
+ \curl_close($ch);
if ($responseStatus >= 400) {
- if (is_array($responseBody)) {
- throw new \Exception(json_encode($responseBody));
+ if (\is_array($responseBody)) {
+ throw new \Exception(\json_encode($responseBody));
} else {
throw new \Exception($responseStatus.': '.$responseBody);
}
@@ -151,6 +162,9 @@ protected function call(string $method, string $path = '', array $headers = [],
/**
* Flatten params array to PHP multiple format
+ *
+ * @param array $data
+ * @return array
*/
protected function flatten(array $data, string $prefix = ''): array
{
@@ -159,7 +173,7 @@ protected function flatten(array $data, string $prefix = ''): array
foreach ($data as $key => $value) {
$finalKey = $prefix ? "{$prefix}[{$key}]" : $key;
- if (is_array($value)) {
+ if (\is_array($value)) {
$output += $this->flatten($value, $finalKey);
} else {
$output[$finalKey] = $value;
@@ -204,4 +218,18 @@ public function addWarning(Warning $warning): void
{
$this->warnings[] = $warning;
}
+
+ /**
+ * Completion callback
+ */
+ public function shutdown(): void
+ {
+ }
+
+ /**
+ * Error callback
+ */
+ public function error(): void
+ {
+ }
}
diff --git a/src/Migration/Transfer.php b/src/Migration/Transfer.php
index 7bce6e8..44463f7 100644
--- a/src/Migration/Transfer.php
+++ b/src/Migration/Transfer.php
@@ -4,52 +4,71 @@
class Transfer
{
- public const GROUP_GENERAL = 'general';
+ public const string GROUP_GENERAL = 'general';
- public const GROUP_AUTH = 'auth';
+ public const string GROUP_AUTH = 'auth';
- public const GROUP_STORAGE = 'storage';
+ public const string GROUP_STORAGE = 'storage';
- public const GROUP_FUNCTIONS = 'functions';
+ public const string GROUP_FUNCTIONS = 'functions';
- public const GROUP_DATABASES = 'databases';
+ public const string GROUP_DATABASES = 'databases';
- public const GROUP_SETTINGS = 'settings';
+ public const string GROUP_SETTINGS = 'settings';
- public const GROUP_AUTH_RESOURCES = [Resource::TYPE_USER, Resource::TYPE_TEAM, Resource::TYPE_MEMBERSHIP, Resource::TYPE_HASH];
-
- public const GROUP_STORAGE_RESOURCES = [Resource::TYPE_FILE, Resource::TYPE_BUCKET];
+ public const array GROUP_AUTH_RESOURCES = [
+ Resource::TYPE_USER,
+ Resource::TYPE_TEAM,
+ Resource::TYPE_MEMBERSHIP,
+ Resource::TYPE_HASH
+ ];
- public const GROUP_FUNCTIONS_RESOURCES = [Resource::TYPE_FUNCTION, Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT];
+ public const array GROUP_STORAGE_RESOURCES = [
+ Resource::TYPE_FILE,
+ Resource::TYPE_BUCKET
+ ];
- public const GROUP_DATABASES_RESOURCES = [Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION, Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE, Resource::TYPE_DOCUMENT];
+ public const array GROUP_FUNCTIONS_RESOURCES = [
+ Resource::TYPE_FUNCTION,
+ Resource::TYPE_ENVIRONMENT_VARIABLE,
+ Resource::TYPE_DEPLOYMENT
+ ];
- public const GROUP_SETTINGS_RESOURCES = [];
+ public const array GROUP_DATABASES_RESOURCES = [
+ Resource::TYPE_DATABASE,
+ Resource::TYPE_COLLECTION,
+ Resource::TYPE_INDEX,
+ Resource::TYPE_ATTRIBUTE,
+ Resource::TYPE_DOCUMENT
+ ];
- public const ALL_PUBLIC_RESOURCES = [
- Resource::TYPE_USER, Resource::TYPE_TEAM,
- Resource::TYPE_MEMBERSHIP, Resource::TYPE_FILE,
- Resource::TYPE_BUCKET, Resource::TYPE_FUNCTION,
- Resource::TYPE_ENVIRONMENT_VARIABLE, Resource::TYPE_DEPLOYMENT,
- Resource::TYPE_DATABASE, Resource::TYPE_COLLECTION,
- Resource::TYPE_INDEX, Resource::TYPE_ATTRIBUTE,
+ public const array GROUP_SETTINGS_RESOURCES = [];
+
+ public const array ALL_PUBLIC_RESOURCES = [
+ Resource::TYPE_USER,
+ Resource::TYPE_TEAM,
+ Resource::TYPE_MEMBERSHIP,
+ Resource::TYPE_FILE,
+ Resource::TYPE_BUCKET,
+ Resource::TYPE_FUNCTION,
+ Resource::TYPE_ENVIRONMENT_VARIABLE,
+ Resource::TYPE_DEPLOYMENT,
+ Resource::TYPE_DATABASE,
+ Resource::TYPE_COLLECTION,
+ Resource::TYPE_INDEX,
+ Resource::TYPE_ATTRIBUTE,
Resource::TYPE_DOCUMENT,
];
- public const STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB
-
- public function __construct(Source $source, Destination $destination)
- {
- $this->source = $source;
- $this->destination = $destination;
- $this->cache = new Cache();
-
- $this->source->registerCache($this->cache);
- $this->destination->registerCache($this->cache);
- $this->destination->setSource($source);
+ public const array ROOT_RESOURCES = [
+ Resource::TYPE_BUCKET,
+ Resource::TYPE_DATABASE,
+ Resource::TYPE_FUNCTION,
+ Resource::TYPE_USER,
+ Resource::TYPE_TEAM,
+ ];
- return $this;
- }
+ public const int STORAGE_MAX_CHUNK_SIZE = 1024 * 1024 * 5; // 5MB
protected Source $source;
@@ -62,9 +81,35 @@ public function __construct(Source $source, Destination $destination)
*/
protected Cache $cache;
+ /**
+ * @var array
+ */
+ protected array $options = [];
+
+ /**
+ * @var array
+ */
+ protected array $events = [];
+
+ /**
+ * @var array
+ */
protected array $resources = [];
- public function getStatusCounters()
+ public function __construct(Source $source, Destination $destination)
+ {
+ $this->source = $source;
+ $this->destination = $destination;
+ $this->cache = new Cache();
+
+ $this->source->registerCache($this->cache);
+ $this->destination->registerCache($this->cache);
+ $this->destination->setSource($source);
+
+ return $this;
+ }
+
+ public function getStatusCounters(): array
{
$status = [];
@@ -89,7 +134,6 @@ public function getStatusCounters()
foreach ($this->cache->getAll() as $resources) {
foreach ($resources as $resource) {
- /** @var resource $resource */
if (isset($status[$resource->getName()])) {
$status[$resource->getName()][$resource->getStatus()]++;
if ($status[$resource->getName()]['pending'] > 0) {
@@ -101,15 +145,13 @@ public function getStatusCounters()
// Process Destination Errors
foreach ($this->destination->getErrors() as $error) {
- /** @var Exception $error */
if (isset($status[$error->getResourceGroup()])) {
$status[$error->getResourceGroup()][Resource::STATUS_ERROR]++;
}
}
- // Process Source Errprs
+ // Process source errors
foreach ($this->source->getErrors() as $error) {
- /** @var Exception $error */
if (isset($status[$error->getResourceGroup()])) {
$status[$error->getResourceGroup()][Resource::STATUS_ERROR]++;
}
@@ -135,11 +177,23 @@ public function getStatusCounters()
/**
* Transfer Resources between adapters
+ *
+ * @param array $resources Resources to transfer
+ * @param callable $callback Callback to run after transfer
+ * @param string|null $rootResourceId Root resource ID, If enabled you can only transfer a single root resource
+ * @throws \Exception
*/
- public function run(array $resources, callable $callback): void
- {
+ public function run(
+ array $resources,
+ callable $callback,
+ string $rootResourceId = null,
+ string $rootResourceType = null,
+ ): void {
// Allows you to push entire groups if you want.
$computedResources = [];
+ $rootResourceId = $rootResourceId ?? '';
+ $rootResourceType = $rootResourceType ?? '';
+
foreach ($resources as $resource) {
if (is_array($resource)) {
$computedResources = array_merge($computedResources, $resource);
@@ -150,8 +204,34 @@ public function run(array $resources, callable $callback): void
$computedResources = array_map('strtolower', $computedResources);
+ if ($rootResourceId !== '') {
+ if ($rootResourceType === '') {
+ throw new \Exception('Resource type must be set when resource ID is set.');
+ }
+
+ if(!in_array($rootResourceType, self::ROOT_RESOURCES)) {
+ throw new \Exception('Resource type must be one of ' . implode(', ', self::ROOT_RESOURCES));
+ }
+
+ $rootResources = \array_intersect($computedResources, self::ROOT_RESOURCES);
+
+ if (\count($rootResources) > 1) {
+ throw new \Exception('Multiple root resources found. Only one root resource can be transferred at a time if using $rootResourceId.');
+ }
+
+ if (\count($rootResources) === 0) {
+ throw new \Exception('No root resources found.');
+ }
+ }
+
$this->resources = $computedResources;
- $this->destination->run($computedResources, $callback, $this->source);
+
+ $this->destination->run(
+ $computedResources,
+ $callback,
+ $rootResourceId,
+ $rootResourceType,
+ );
}
/**
@@ -174,7 +254,8 @@ public function getCurrentResource(): string
/**
* Get Transfer Report
*
- * @param string $statusLevel If no status level is provided, all status types will be returned.
+ * @param string $statusLevel If no status level is provided, all status types will be returned.
+ * @return array>
*/
public function getReport(string $statusLevel = ''): array
{
@@ -199,4 +280,25 @@ public function getReport(string $statusLevel = ''): array
return $report;
}
+
+ /**
+ * @throws \Exception
+ */
+ public static function extractServices(array $services): array
+ {
+ $resources = [];
+ foreach ($services as $service) {
+ $resources = match ($service) {
+ self::GROUP_FUNCTIONS => array_merge($resources, self::GROUP_FUNCTIONS_RESOURCES),
+ self::GROUP_STORAGE => array_merge($resources, self::GROUP_STORAGE_RESOURCES),
+ self::GROUP_GENERAL => array_merge($resources, []),
+ self::GROUP_AUTH => array_merge($resources, self::GROUP_AUTH_RESOURCES),
+ self::GROUP_DATABASES => array_merge($resources, self::GROUP_DATABASES_RESOURCES),
+ self::GROUP_SETTINGS => array_merge($resources, self::GROUP_SETTINGS_RESOURCES),
+ default => throw new \Exception('No service group found'),
+ };
+ }
+
+ return $resources;
+ }
}
diff --git a/tests/Migration/E2E/Sources/Base.php b/tests/Migration/E2E/Sources/Base.php
index c5a7f23..7b5bd40 100644
--- a/tests/Migration/E2E/Sources/Base.php
+++ b/tests/Migration/E2E/Sources/Base.php
@@ -7,7 +7,7 @@
use Utopia\Migration\Resource;
use Utopia\Migration\Source;
use Utopia\Migration\Transfer;
-use Utopia\Tests\E2E\Adapters\Mock;
+use Utopia\Tests\Unit\Adapters\MockDestination;
abstract class Base extends TestCase
{
@@ -23,7 +23,7 @@ protected function setUp(): void
throw new \Exception('Source not set');
}
- $this->destination = new Mock();
+ $this->destination = new MockDestination();
$this->transfer = new Transfer($this->source, $this->destination);
}
diff --git a/tests/Migration/E2E/Sources/NHostTest.php b/tests/Migration/E2E/Sources/NHostTest.php
index cf75c90..b0d153b 100644
--- a/tests/Migration/E2E/Sources/NHostTest.php
+++ b/tests/Migration/E2E/Sources/NHostTest.php
@@ -2,12 +2,18 @@
namespace Utopia\Tests\E2E\Sources;
+use PHPUnit\Framework\Attributes\Depends;
use Utopia\Migration\Destination;
use Utopia\Migration\Resource;
+use Utopia\Migration\Resources\Auth\User;
+use Utopia\Migration\Resources\Database\Collection;
+use Utopia\Migration\Resources\Database\Database;
+use Utopia\Migration\Resources\Storage\Bucket;
+use Utopia\Migration\Resources\Storage\File;
use Utopia\Migration\Source;
use Utopia\Migration\Sources\NHost;
use Utopia\Migration\Transfer;
-use Utopia\Tests\E2E\Adapters\Mock;
+use Utopia\Tests\Unit\Adapters\MockDestination;
class NHostTest extends Base
{
@@ -17,6 +23,9 @@ class NHostTest extends Base
protected ?Destination $destination = null;
+ /**
+ * @throws \Exception
+ */
protected function setUp(): void
{
// Check DB is online and ready
@@ -28,7 +37,7 @@ protected function setUp(): void
$pdo = new \PDO('pgsql:host=nhost-db'.';port=5432;dbname=postgres', 'postgres', 'postgres');
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
- if ($pdo && $pdo->query('SELECT 1')->fetchColumn() === 1) {
+ if ($pdo->query('SELECT 1')->fetchColumn() === 1) {
break;
} else {
var_dump('DB was offline, waiting 1s then retrying.');
@@ -51,7 +60,7 @@ protected function setUp(): void
$this->call('GET', 'http://nhost-storage/', ['Content-Type' => 'text/plain']);
break;
- } catch (\Exception $e) {
+ } catch (\Exception) {
}
sleep(5);
@@ -73,10 +82,13 @@ protected function setUp(): void
$this->source->pdo = new \PDO('pgsql:host=nhost-db'.';port=5432;dbname=postgres', 'postgres', 'postgres');
$this->source->storageURL = 'http://nhost-storage';
- $this->destination = new Mock();
+ $this->destination = new MockDestination();
$this->transfer = new Transfer($this->source, $this->destination);
}
+ /**
+ * @throws \Exception
+ */
public function testSourceReport()
{
// Test report all
@@ -90,8 +102,9 @@ public function testSourceReport()
}
/**
- * @depends testSourceReport
+ * @throws \Exception
*/
+ #[Depends('testSourceReport')]
public function testRunTransfer($state)
{
$this->transfer->run(
@@ -99,38 +112,58 @@ public function testRunTransfer($state)
function () {}
);
- $this->assertEquals(0, count($this->transfer->getReport('error')));
+ $this->assertCount(0, $this->transfer->getReport('error'));
return array_merge($state, [
'transfer' => $this->transfer,
'source' => $this->source,
+ 'destination' => $this->destination,
]);
}
- /**
- * @depends testRunTransfer
- */
- public function testValidateTransfer($state)
+ #[Depends('testRunTransfer')]
+ public function testValidateSourceErrors($state)
{
- $statusCounters = $state['transfer']->getStatusCounters();
+ /** @var Transfer $transfer */
+ $transfer = $state['transfer'];
+
+ /** @var Source $source */
+ $source = $state['source'];
+
+ $statusCounters = $transfer->getStatusCounters();
$this->assertNotEmpty($statusCounters);
- foreach ($statusCounters as $resource => $counters) {
- $this->assertNotEmpty($counters);
+ $errors = $source->getErrors();
- if ($counters[Resource::STATUS_ERROR] > 0) {
- $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors');
+ if (!empty($errors)) {
+ $this->fail('[Source] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT));
+ }
- return;
- }
+ return $state;
+ }
+
+ #[Depends('testValidateSourceErrors')]
+ public function testValidateDestinationErrors($state)
+ {
+ /** @var Transfer $transfer */
+ $transfer = $state['transfer'];
+
+ /** @var Destination $destination */
+ $destination = $state['destination'];
+
+ $statusCounters = $transfer->getStatusCounters();
+ $this->assertNotEmpty($statusCounters);
+
+ $errors = $destination->getErrors();
+
+ if (!empty($errors)) {
+ $this->fail('[Destination] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT));
}
return $state;
}
- /**
- * @depends testValidateTransfer
- */
+ #[Depends('testValidateDestinationErrors')]
public function testValidateUserTransfer($state): void
{
// Find known user
@@ -138,7 +171,7 @@ public function testValidateUserTransfer($state): void
$foundUser = null;
foreach ($users as $user) {
- /** @var \Utopia\Migration\Resources\Auth\User $user */
+ /** @var User $user */
if ($user->getEmail() === 'test@test.com') {
$foundUser = $user;
}
@@ -148,8 +181,6 @@ public function testValidateUserTransfer($state): void
if (! $foundUser) {
$this->fail('User "test@test.com" not found');
-
- return;
}
$this->assertEquals('success', $foundUser->getStatus());
@@ -158,9 +189,7 @@ public function testValidateUserTransfer($state): void
$this->assertEquals('test@test.com', $foundUser->getUsername());
}
- /**
- * @depends testValidateTransfer
- */
+ #[Depends('testValidateDestinationErrors')]
public function testValidateDatabaseTransfer($state)
{
// Find known database
@@ -168,8 +197,8 @@ public function testValidateDatabaseTransfer($state)
$foundDatabase = null;
foreach ($databases as $database) {
- /** @var \Utopia\Migration\Resources\Database $database */
- if ($database->getDBName() === 'public') {
+ /** @var Database $database */
+ if ($database->getDatabaseName() === 'public') {
$foundDatabase = $database;
}
@@ -178,12 +207,10 @@ public function testValidateDatabaseTransfer($state)
if (! $foundDatabase) {
$this->fail('Database "public" not found');
-
- return;
}
$this->assertEquals('success', $foundDatabase->getStatus());
- $this->assertEquals('public', $foundDatabase->getDBName());
+ $this->assertEquals('public', $foundDatabase->getDatabaseName());
$this->assertEquals('public', $foundDatabase->getId());
// Find known collection
@@ -191,7 +218,7 @@ public function testValidateDatabaseTransfer($state)
$foundCollection = null;
foreach ($collections as $collection) {
- /** @var \Utopia\Migration\Resources\Database\Collection $collection */
+ /** @var Collection $collection */
if ($collection->getCollectionName() === 'TestTable') {
$foundCollection = $collection;
@@ -201,8 +228,6 @@ public function testValidateDatabaseTransfer($state)
if (! $foundCollection) {
$this->fail('Collection "TestTable" not found');
-
- return;
}
$this->assertEquals('success', $foundCollection->getStatus());
@@ -213,9 +238,7 @@ public function testValidateDatabaseTransfer($state)
return $state;
}
- /**
- * @depends testValidateDatabaseTransfer
- */
+ #[Depends('testValidateDatabaseTransfer')]
public function testDatabaseFunctionalDefaultsWarn($state): void
{
// Find known collection
@@ -223,7 +246,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void
$foundCollection = null;
foreach ($collections as $collection) {
- /** @var \Utopia\Migration\Resources\Database\Collection $collection */
+ /** @var Collection $collection */
if ($collection->getCollectionName() === 'FunctionalDefaultTestTable') {
$foundCollection = $collection;
}
@@ -233,8 +256,6 @@ public function testDatabaseFunctionalDefaultsWarn($state): void
if (! $foundCollection) {
$this->fail('Collection "FunctionalDefaultTestTable" not found');
-
- return;
}
$this->assertEquals('warning', $foundCollection->getStatus());
@@ -243,9 +264,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void
$this->assertEquals('public', $foundCollection->getDatabase()->getId());
}
- /**
- * @depends testValidateTransfer
- */
+ #[Depends('testValidateDatabaseTransfer')]
public function testValidateStorageTransfer($state): void
{
// Find known bucket
@@ -253,7 +272,7 @@ public function testValidateStorageTransfer($state): void
$foundBucket = null;
foreach ($buckets as $bucket) {
- /** @var \Utopia\Migration\Resources\Bucket $bucket */
+ /** @var Bucket $bucket */
if ($bucket->getId() === 'default') {
$foundBucket = $bucket;
}
@@ -263,8 +282,6 @@ public function testValidateStorageTransfer($state): void
if (! $foundBucket) {
$this->fail('Bucket "default" not found');
-
- return;
}
$this->assertEquals('success', $foundBucket->getStatus());
@@ -275,7 +292,7 @@ public function testValidateStorageTransfer($state): void
$foundFile = null;
foreach ($files as $file) {
- /** @var \Utopia\Migration\Resources\File $file */
+ /** @var File $file */
if ($file->getFileName() === 'tulips.png') {
$foundFile = $file;
}
@@ -285,10 +302,8 @@ public function testValidateStorageTransfer($state): void
if (! $foundFile) {
$this->fail('File "tulips.png" not found');
-
- return;
}
- /** @var \Utopia\Migration\Resources\Storage\File $foundFile */
+ /** @var File $foundFile */
$this->assertEquals('success', $foundFile->getStatus());
$this->assertEquals('tulips.png', $foundFile->getFileName());
$this->assertEquals('default', $foundFile->getBucket()->getId());
diff --git a/tests/Migration/E2E/Sources/SupabaseTest.php b/tests/Migration/E2E/Sources/SupabaseTest.php
index ba09804..53096a5 100644
--- a/tests/Migration/E2E/Sources/SupabaseTest.php
+++ b/tests/Migration/E2E/Sources/SupabaseTest.php
@@ -2,12 +2,19 @@
namespace Utopia\Tests\E2E\Sources;
+use PHPUnit\Framework\Attributes\Depends;
use Utopia\Migration\Destination;
use Utopia\Migration\Resource;
+use Utopia\Migration\Resources\Auth\User;
+use Utopia\Migration\Resources\Database\Collection;
+use Utopia\Migration\Resources\Database\Database;
+use Utopia\Migration\Resources\Database\Document;
+use Utopia\Migration\Resources\Storage\Bucket;
+use Utopia\Migration\Resources\Storage\File;
use Utopia\Migration\Source;
use Utopia\Migration\Sources\Supabase;
use Utopia\Migration\Transfer;
-use Utopia\Tests\E2E\Adapters\Mock;
+use Utopia\Tests\Unit\Adapters\MockDestination;
class SupabaseTest extends Base
{
@@ -17,6 +24,9 @@ class SupabaseTest extends Base
protected ?Destination $destination = null;
+ /**
+ * @throws \Exception
+ */
protected function setUp(): void
{
// Check DB is online and ready
@@ -28,12 +38,12 @@ protected function setUp(): void
$pdo = new \PDO('pgsql:host=supabase-db'.';port=5432;dbname=postgres', 'postgres', 'postgres');
$pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
- if ($pdo && $pdo->query('SELECT 1')->fetchColumn() === 1) {
+ if ($pdo->query('SELECT 1')->fetchColumn() === 1) {
break;
} else {
var_dump('DB was offline, waiting 1s then retrying.');
}
- } catch (\PDOException $e) {
+ } catch (\PDOException) {
}
sleep(1);
@@ -53,10 +63,13 @@ protected function setUp(): void
'postgres'
);
- $this->destination = new Mock();
+ $this->destination = new MockDestination();
$this->transfer = new Transfer($this->source, $this->destination);
}
+ /**
+ * @throws \Exception
+ */
public function testSourceReport()
{
// Test report all
@@ -70,46 +83,68 @@ public function testSourceReport()
}
/**
- * @depends testSourceReport
+ * @throws \Exception
*/
+ #[Depends('testSourceReport')]
public function testRunTransfer($state)
{
- $this->transfer->run($this->source->getSupportedResources(),
+ $this->transfer->run(
+ $this->source->getSupportedResources(),
function () {}
);
- $this->assertEquals(0, count($this->transfer->getReport('error')));
+ $this->assertCount(0, $this->transfer->getReport('error'));
return array_merge($state, [
'transfer' => $this->transfer,
'source' => $this->source,
+ 'destination' => $this->destination,
]);
}
- /**
- * @depends testRunTransfer
- */
- public function testValidateTransfer($state)
+ #[Depends('testRunTransfer')]
+ public function testValidateSourceErrors($state)
{
- $statusCounters = $state['transfer']->getStatusCounters();
+ /** @var Transfer $transfer */
+ $transfer = $state['transfer'];
+
+ /** @var Source $source */
+ $source = $state['source'];
+
+ $statusCounters = $transfer->getStatusCounters();
$this->assertNotEmpty($statusCounters);
- foreach ($statusCounters as $resource => $counters) {
- $this->assertNotEmpty($counters);
+ $errors = $source->getErrors();
- if ($counters[Resource::STATUS_ERROR] > 0) {
- $this->fail('Resource '.$resource.' has '.$counters[Resource::STATUS_ERROR].' errors');
+ if (!empty($errors)) {
+ $this->fail('[Source] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT));
+ }
- return;
- }
+ return $state;
+ }
+
+ #[Depends('testValidateSourceErrors')]
+ public function testValidateDestinationErrors($state)
+ {
+ /** @var Transfer $transfer */
+ $transfer = $state['transfer'];
+
+ /** @var Destination $destination */
+ $destination = $state['destination'];
+
+ $statusCounters = $transfer->getStatusCounters();
+ $this->assertNotEmpty($statusCounters);
+
+ $errors = $destination->getErrors();
+
+ if (!empty($errors)) {
+ $this->fail('[Destination] Failed: ' . \json_encode($errors, JSON_PRETTY_PRINT));
}
return $state;
}
- /**
- * @depends testValidateTransfer
- */
+ #[Depends('testValidateDestinationErrors')]
public function testValidateUserTransfer($state): void
{
// Find known user
@@ -118,7 +153,7 @@ public function testValidateUserTransfer($state): void
$foundUser = null;
foreach ($users as $user) {
- /** @var \Utopia\Migration\Resources\Auth\User $user */
+ /** @var User $user */
if ($user->getEmail() == 'albert.kihn95@yahoo.com') {
$foundUser = $user;
@@ -128,8 +163,6 @@ public function testValidateUserTransfer($state): void
if (! $foundUser) {
$this->fail('User "albert.kihn95@yahoo.com" not found');
-
- return;
}
$this->assertEquals('success', $foundUser->getStatus());
@@ -137,9 +170,7 @@ public function testValidateUserTransfer($state): void
$this->assertEquals('bcrypt', $foundUser->getPasswordHash()->getAlgorithm());
}
- /**
- * @depends testValidateTransfer
- */
+ #[Depends('testValidateDestinationErrors')]
public function testValidateDatabaseTransfer($state)
{
// Find known database
@@ -148,8 +179,8 @@ public function testValidateDatabaseTransfer($state)
$foundDatabase = null;
foreach ($databases as $database) {
- /** @var \Utopia\Migration\Resources\Database $database */
- if ($database->getDBName() === 'public') {
+ /** @var Database $database */
+ if ($database->getDatabaseName() === 'public') {
$foundDatabase = $database;
break;
@@ -158,12 +189,10 @@ public function testValidateDatabaseTransfer($state)
if (! $foundDatabase) {
$this->fail('Database "public" not found');
-
- return;
}
$this->assertEquals('success', $foundDatabase->getStatus());
- $this->assertEquals('public', $foundDatabase->getDBName());
+ $this->assertEquals('public', $foundDatabase->getDatabaseName());
$this->assertEquals('public', $foundDatabase->getId());
// Find Known Collections
@@ -173,8 +202,8 @@ public function testValidateDatabaseTransfer($state)
$foundCollection = null;
foreach ($collections as $collection) {
- /** @var \Utopia\Migration\Resources\Database\Collection $collection */
- if ($collection->getDatabase()->getDBName() === 'public' && $collection->getCollectionName() === 'test') {
+ /** @var Collection $collection */
+ if ($collection->getDatabase()->getDatabaseName() === 'public' && $collection->getCollectionName() === 'test') {
$foundCollection = $collection;
break;
@@ -183,13 +212,11 @@ public function testValidateDatabaseTransfer($state)
if (! $foundCollection) {
$this->fail('Collection "test" not found');
-
- return;
}
$this->assertEquals('success', $foundCollection->getStatus());
$this->assertEquals('test', $foundCollection->getCollectionName());
- $this->assertEquals('public', $foundCollection->getDatabase()->getDBName());
+ $this->assertEquals('public', $foundCollection->getDatabase()->getDatabaseName());
$this->assertEquals('public', $foundCollection->getDatabase()->getId());
// Find Known Documents
@@ -199,8 +226,8 @@ public function testValidateDatabaseTransfer($state)
$foundDocument = null;
foreach ($documents as $document) {
- /** @var \Utopia\Migration\Resources\Database\Document $document */
- if ($document->getCollection()->getDatabase()->getDBName() === 'public' && $document->getCollection()->getCollectionName() === 'test') {
+ /** @var Document $document */
+ if ($document->getCollection()->getDatabase()->getDatabaseName() === 'public' && $document->getCollection()->getCollectionName() === 'test') {
$foundDocument = $document;
}
@@ -209,8 +236,6 @@ public function testValidateDatabaseTransfer($state)
if (! $foundDocument) {
$this->fail('Document "1" not found');
-
- return;
}
$this->assertEquals('success', $foundDocument->getStatus());
@@ -218,9 +243,7 @@ public function testValidateDatabaseTransfer($state)
return $state;
}
- /**
- * @depends testValidateDatabaseTransfer
- */
+ #[Depends('testValidateDatabaseTransfer')]
public function testDatabaseFunctionalDefaultsWarn($state): void
{
// Find known collection
@@ -228,7 +251,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void
$foundCollection = null;
foreach ($collections as $collection) {
- /** @var \Utopia\Migration\Resources\Database\Collection $collection */
+ /** @var Collection $collection */
if ($collection->getCollectionName() === 'FunctionalDefaultTestTable') {
$foundCollection = $collection;
}
@@ -238,8 +261,6 @@ public function testDatabaseFunctionalDefaultsWarn($state): void
if (! $foundCollection) {
$this->fail('Collection "FunctionalDefaultTestTable" not found');
-
- return;
}
$this->assertEquals('warning', $foundCollection->getStatus());
@@ -248,9 +269,7 @@ public function testDatabaseFunctionalDefaultsWarn($state): void
$this->assertEquals('public', $foundCollection->getDatabase()->getId());
}
- /**
- * @depends testValidateTransfer
- */
+ #[Depends('testValidateDestinationErrors')]
public function testValidateStorageTransfer($state): void
{
// Find known bucket
@@ -260,7 +279,7 @@ public function testValidateStorageTransfer($state): void
$foundBucket = null;
foreach ($buckets as $bucket) {
- /** @var \Utopia\Migration\Resources\Storage\Bucket $bucket */
+ /** @var Bucket $bucket */
if ($bucket->getBucketName() === 'Test Bucket 1') {
$foundBucket = $bucket;
}
@@ -270,8 +289,6 @@ public function testValidateStorageTransfer($state): void
if (! $foundBucket) {
$this->fail('Bucket "Test Bucket 1" not found');
-
- return;
}
$this->assertEquals('success', $foundBucket->getStatus());
@@ -283,7 +300,7 @@ public function testValidateStorageTransfer($state): void
$foundFile = null;
foreach ($files as $file) {
- /** @var \Utopia\Migration\Resources\File $file */
+ /** @var File $file */
if ($file->getFileName() === 'tulips.png') {
$foundFile = $file;
}
@@ -293,10 +310,8 @@ public function testValidateStorageTransfer($state): void
if (! $foundFile) {
$this->fail('File "tulips.png" not found');
-
- return;
}
- /** @var \Utopia\Migration\Resources\Storage\File $foundFile */
+ /** @var File $foundFile */
$this->assertEquals('success', $foundFile->getStatus());
$this->assertEquals('tulips.png', $foundFile->getFileName());
$this->assertEquals('image/png', $foundFile->getMimeType());
diff --git a/tests/Migration/E2E/Adapters/Mock.php b/tests/Migration/Unit/Adapters/MockDestination.php
similarity index 57%
rename from tests/Migration/E2E/Adapters/Mock.php
rename to tests/Migration/Unit/Adapters/MockDestination.php
index e2c318a..b6970fa 100644
--- a/tests/Migration/E2E/Adapters/Mock.php
+++ b/tests/Migration/Unit/Adapters/MockDestination.php
@@ -1,17 +1,34 @@
data[$group] ?? [];
+ }
+
+ public function getResourceTypeData(string $group, string $resourceType): array
+ {
+ return array_keys($this->data[$group][$resourceType]) ?? [];
+ }
+
+ public function getResourceById(string $group, string $resourceType, string $resourceId): ?Resource
+ {
+ return $this->data[$group][$resourceType][$resourceId] ?? null;
+ }
+
public static function getName(): string
{
- return 'Mock';
+ return 'MockDestination';
}
public static function getSupportedResources(): array
@@ -37,12 +54,12 @@ public static function getSupportedResources(): array
public function import(array $resources, callable $callback): void
{
foreach ($resources as $resource) {
- /** @var resource $resource */
+ /** @var Resource $resource */
switch ($resource->getName()) {
case 'Deployment':
/** @var Deployment $resource */
if ($resource->getStart() === 0) {
- $this->data[$resource->getGroup()][$resource->getName()][$resource->getInternalId()] = $resource->asArray();
+ $this->data[$resource->getGroup()][$resource->getName()][$resource->getId()] = $resource;
}
// file_put_contents($this->path . 'deployments/' . $resource->getId() . '.tar.gz', $resource->getData(), FILE_APPEND);
@@ -52,6 +69,15 @@ public function import(array $resources, callable $callback): void
break;
}
+ if (!key_exists($resource->getGroup(), $this->data)) {
+ $this->data[$resource->getGroup()] = [];
+ }
+
+ if (!key_exists($resource->getName(), $this->data[$resource->getGroup()])) {
+ $this->data[$resource->getGroup()][$resource->getName()] = [];
+ }
+
+ $this->data[$resource->getGroup()][$resource->getName()][$resource->getId()] = $resource;
$resource->setStatus(Resource::STATUS_SUCCESS);
$this->cache->update($resource);
}
@@ -59,7 +85,7 @@ public function import(array $resources, callable $callback): void
$callback($resources);
}
- public function report(array $groups = []): array
+ public function report(array $resources = []): array
{
return [];
}
diff --git a/tests/Migration/Unit/Adapters/MockSource.php b/tests/Migration/Unit/Adapters/MockSource.php
new file mode 100644
index 0000000..66f22ac
--- /dev/null
+++ b/tests/Migration/Unit/Adapters/MockSource.php
@@ -0,0 +1,155 @@
+getGroup(), $this->mockResources)) {
+ $this->mockResources[$resource->getGroup()] = [];
+ }
+
+ if (!key_exists($resource->getName(), $this->mockResources[$resource->getGroup()])) {
+ $this->mockResources[$resource->getGroup()][$resource->getName()] = [];
+ }
+
+ $this->mockResources[$resource->getGroup()][$resource->getName()][$resource->getId()] = $resource;
+ }
+
+ public function getMockResources(): array
+ {
+ return $this->mockResources;
+ }
+
+ public function getMockResourcesByType(string $group, string $type): array
+ {
+ return array_values($this->mockResources[$group][$type]) ?? [];
+ }
+
+ public function getMockResourceById(string $group, string $type, string $id): ?Resource
+ {
+ return $this->mockResources[$group][$type][$id] ?? null;
+ }
+
+ public function clearMockResources(): void
+ {
+ $this->mockResources = [];
+ }
+
+ private function handleResourceTransfer(string $group, string $type): void
+ {
+ if (in_array($type, Transfer::ROOT_RESOURCES) && !empty($this->rootResourceId)) {
+ $this->callback([$this->getMockResourceById($group, $type, $this->rootResourceId)]);
+ return;
+ }
+
+ $resources = $this->getMockResourcesByType($group, $type) ?? [];
+ $this->callback($resources);
+ return;
+ }
+
+ public static function getName(): string
+ {
+ return 'MockSource';
+ }
+
+ public static function getSupportedResources(): array
+ {
+ return [
+ Resource::TYPE_ATTRIBUTE,
+ Resource::TYPE_BUCKET,
+ Resource::TYPE_COLLECTION,
+ Resource::TYPE_DATABASE,
+ Resource::TYPE_DOCUMENT,
+ Resource::TYPE_FILE,
+ Resource::TYPE_FUNCTION,
+ Resource::TYPE_DEPLOYMENT,
+ Resource::TYPE_HASH,
+ Resource::TYPE_INDEX,
+ Resource::TYPE_USER,
+ Resource::TYPE_ENVIRONMENT_VARIABLE,
+ Resource::TYPE_TEAM,
+ Resource::TYPE_MEMBERSHIP,
+ ];
+ }
+
+ public function report(array $resources = []): array
+ {
+ return [];
+ }
+
+ /**
+ * Export Auth Group
+ *
+ * @param int $batchSize Max 100
+ * @param string[] $resources Resources to export
+ */
+ protected function exportGroupAuth(int $batchSize, array $resources): void
+ {
+ foreach (Transfer::GROUP_AUTH_RESOURCES as $resource) {
+ if (!\in_array($resource, $resources)) {
+ continue;
+ }
+
+ $this->handleResourceTransfer(Transfer::GROUP_AUTH, $resource);
+ }
+ }
+
+ /**
+ * Export Databases Group
+ *
+ * @param int $batchSize Max 100
+ * @param string[] $resources Resources to export
+ */
+ protected function exportGroupDatabases(int $batchSize, array $resources): void
+ {
+ foreach (Transfer::GROUP_DATABASES_RESOURCES as $resource) {
+ if (!\in_array($resource, $resources)) {
+ continue;
+ }
+
+ $this->handleResourceTransfer(Transfer::GROUP_DATABASES, $resource);
+ }
+ }
+
+ /**
+ * Export Storage Group
+ *
+ * @param int $batchSize Max 5
+ * @param string[] $resources Resources to export
+ */
+ protected function exportGroupStorage(int $batchSize, array $resources): void
+ {
+ foreach (Transfer::GROUP_STORAGE_RESOURCES as $resource) {
+ if (!\in_array($resource, $resources)) {
+ continue;
+ }
+
+ $this->handleResourceTransfer(Transfer::GROUP_STORAGE, $resource);
+ }
+ }
+
+ /**
+ * Export Functions Group
+ *
+ * @param int $batchSize Max 100
+ * @param string[] $resources Resources to export
+ */
+ protected function exportGroupFunctions(int $batchSize, array $resources): void
+ {
+ foreach (Transfer::GROUP_FUNCTIONS_RESOURCES as $resource) {
+ if (!\in_array($resource, $resources)) {
+ continue;
+ }
+
+ $this->handleResourceTransfer(Transfer::GROUP_FUNCTIONS, $resource);
+ }
+ }
+}
diff --git a/tests/Migration/Unit/General/TransferTest.php b/tests/Migration/Unit/General/TransferTest.php
new file mode 100644
index 0000000..0283437
--- /dev/null
+++ b/tests/Migration/Unit/General/TransferTest.php
@@ -0,0 +1,65 @@
+source = new MockSource();
+ $this->destination = new MockDestination();
+
+ $this->transfer = new Transfer(
+ $this->source,
+ $this->destination
+ );
+ }
+
+ /**
+ * @throws \Exception
+ */
+ public function testRootResourceId(): void
+ {
+ /**
+ * TEST FOR FAILURE
+ * Make sure we can't create a transfer with multiple root resources when supplying a rootResourceId
+ */
+ try {
+ $this->transfer->run([Resource::TYPE_USER, Resource::TYPE_DATABASE], function () {}, 'rootResourceId');
+ $this->fail('Multiple root resources should not be allowed');
+ } catch (\Exception $e) {
+ $this->assertEquals('Resource type must be set when resource ID is set.', $e->getMessage());
+ }
+
+ $this->source->pushMockResource(new Database('test', 'test'));
+ $this->source->pushMockResource(new Database('test2', 'test'));
+
+ /**
+ * TEST FOR SUCCESS
+ */
+ $this->transfer->run(
+ [Resource::TYPE_DATABASE],
+ function () {},
+ 'test',
+ Resource::TYPE_DATABASE
+ );
+ $this->assertCount(1, $this->destination->getResourceTypeData(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE));
+
+ $database = $this->destination->getResourceById(Transfer::GROUP_DATABASES, Resource::TYPE_DATABASE, 'test');
+ /** @var Database $database */
+ $this->assertNotNull($database);
+ $this->assertEquals('test', $database->getDatabaseName());
+ $this->assertEquals('test', $database->getId());
+ }
+}