diff --git a/composer.json b/composer.json index b24984a700..c238422a3f 100644 --- a/composer.json +++ b/composer.json @@ -10,7 +10,6 @@ "require-dev": { "anourvalar/eloquent-serialize": "^1.2", "danharrin/livewire-rate-limiting": "^0.3|^1.0", - "doctrine/dbal": "^3.2", "filament/support": "*", "kirschbaum-development/eloquent-power-joins": "^3.0", "larastan/larastan": "^2.2", diff --git a/composer.lock b/composer.lock index 765c65908d..a641edce83 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "282351566cb9fb9c4ee2b530e40b36ec", + "content-hash": "52e2c5aa84e2b1d4b7afb44512079b31", "packages": [], "packages-dev": [ { @@ -129,16 +129,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.300.1", + "version": "3.300.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "fb67a49453c2b1a51268156b70adeba6c619e743" + "reference": "a1202d1a82593fc2cdad54d627e54c518a984738" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/fb67a49453c2b1a51268156b70adeba6c619e743", - "reference": "fb67a49453c2b1a51268156b70adeba6c619e743", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/a1202d1a82593fc2cdad54d627e54c518a984738", + "reference": "a1202d1a82593fc2cdad54d627e54c518a984738", "shasum": "" }, "require": { @@ -218,9 +218,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.300.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.300.3" }, - "time": "2024-02-20T19:05:00+00:00" + "time": "2024-02-22T19:20:21+00:00" }, { "name": "blade-ui-kit/blade-heroicons", @@ -800,212 +800,6 @@ }, "time": "2022-10-27T11:44:00+00:00" }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" - }, - { - "name": "doctrine/dbal", - "version": "3.8.2", - "source": { - "type": "git", - "url": "https://github.com/doctrine/dbal.git", - "reference": "a19a1d05ca211f41089dffcc387733a6875196cb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/a19a1d05ca211f41089dffcc387733a6875196cb", - "reference": "a19a1d05ca211f41089dffcc387733a6875196cb", - "shasum": "" - }, - "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", - "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", - "psr/cache": "^1|^2|^3", - "psr/log": "^1|^2|^3" - }, - "require-dev": { - "doctrine/coding-standard": "12.0.0", - "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.57", - "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.16", - "psalm/plugin-phpunit": "0.18.4", - "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.8.1", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" - }, - "suggest": { - "symfony/console": "For helpful console commands such as SQL execution and import of files." - }, - "bin": [ - "bin/doctrine-dbal" - ], - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\DBAL\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - } - ], - "description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.", - "homepage": "https://www.doctrine-project.org/projects/dbal.html", - "keywords": [ - "abstraction", - "database", - "db2", - "dbal", - "mariadb", - "mssql", - "mysql", - "oci8", - "oracle", - "pdo", - "pgsql", - "postgresql", - "queryobject", - "sasql", - "sql", - "sqlite", - "sqlserver", - "sqlsrv" - ], - "support": { - "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.8.2" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal", - "type": "tidelift" - } - ], - "time": "2024-02-12T18:36:36+00:00" - }, { "name": "doctrine/deprecations", "version": "1.1.3", @@ -1053,97 +847,6 @@ }, "time": "2024-01-30T19:34:25+00:00" }, - { - "name": "doctrine/event-manager", - "version": "2.0.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32", - "reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^4.28" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2022-10-12T20:59:15+00:00" - }, { "name": "doctrine/inflector", "version": "2.0.10", @@ -1570,15 +1273,14 @@ "dist": { "type": "path", "url": "packages/support", - "reference": "781c8793577215de042b467bd1520c9ac877dd28" + "reference": "5d5df437d6ecd6b2f7947e23bb9b199f434980fd" }, "require": { "blade-ui-kit/blade-heroicons": "^2.2.1", - "doctrine/dbal": "^3.2", "ext-intl": "*", - "illuminate/contracts": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", - "illuminate/view": "^10.0|^11.0", + "illuminate/contracts": "^10.45|^11.0", + "illuminate/support": "^10.45|^11.0", + "illuminate/view": "^10.45|^11.0", "livewire/livewire": "^3.2.3", "php": "^8.1", "ryangjchandler/blade-capture-directive": "^0.2|^0.3", @@ -2593,16 +2295,16 @@ }, { "name": "laravel/framework", - "version": "v10.45.0", + "version": "v10.45.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "8b08d8cd79f8093eb51a8c59e21647bedfbf05f2" + "reference": "dcf5d1d722b84ad38a5e053289130b6962f830bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/8b08d8cd79f8093eb51a8c59e21647bedfbf05f2", - "reference": "8b08d8cd79f8093eb51a8c59e21647bedfbf05f2", + "url": "https://api.github.com/repos/laravel/framework/zipball/dcf5d1d722b84ad38a5e053289130b6962f830bd", + "reference": "dcf5d1d722b84ad38a5e053289130b6962f830bd", "shasum": "" }, "require": { @@ -2795,7 +2497,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-02-20T15:32:48+00:00" + "time": "2024-02-21T14:07:36+00:00" }, { "name": "laravel/pint", @@ -4645,16 +4347,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.0.0", + "version": "v5.0.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", - "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/2218c2252c874a4624ab2f613d86ac32d227bc69", + "reference": "2218c2252c874a4624ab2f613d86ac32d227bc69", "shasum": "" }, "require": { @@ -4697,9 +4399,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.1" }, - "time": "2024-01-07T17:17:35+00:00" + "time": "2024-02-21T19:24:10+00:00" }, { "name": "nunomaduro/collision", @@ -5180,16 +4882,16 @@ }, { "name": "orchestra/testbench-core", - "version": "v8.21.1", + "version": "v8.22.1", "source": { "type": "git", "url": "https://github.com/orchestral/testbench-core.git", - "reference": "c6bf49a1b7ef4afe58e5bda676158c4de41f9a81" + "reference": "b0006c092694828f4b0fa409a369b798e5e26f8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/c6bf49a1b7ef4afe58e5bda676158c4de41f9a81", - "reference": "c6bf49a1b7ef4afe58e5bda676158c4de41f9a81", + "url": "https://api.github.com/repos/orchestral/testbench-core/zipball/b0006c092694828f4b0fa409a369b798e5e26f8d", + "reference": "b0006c092694828f4b0fa409a369b798e5e26f8d", "shasum": "" }, "require": { @@ -5268,7 +4970,7 @@ "issues": "https://github.com/orchestral/testbench/issues", "source": "https://github.com/orchestral/testbench-core" }, - "time": "2024-01-22T01:45:47+00:00" + "time": "2024-02-21T23:33:22+00:00" }, { "name": "orchestra/workbench", @@ -5886,21 +5588,21 @@ }, { "name": "phpdocumentor/type-resolver", - "version": "1.8.1", + "version": "1.8.2", "source": { "type": "git", "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353" + "reference": "153ae662783729388a584b4361f2545e4d841e3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353", - "reference": "bc3dc91a5e9b14aa06d1d9e90647c5c5a2cc5353", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", + "reference": "153ae662783729388a584b4361f2545e4d841e3c", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", - "php": "^7.4 || ^8.0", + "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", "phpstan/phpdoc-parser": "^1.13" }, @@ -5938,9 +5640,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.1" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" }, - "time": "2024-01-18T19:15:27+00:00" + "time": "2024-02-23T11:10:43+00:00" }, { "name": "phpmyadmin/sql-parser", @@ -6733,55 +6435,6 @@ }, "time": "2021-10-28T11:13:42+00:00" }, - { - "name": "psr/cache", - "version": "3.0.0", - "source": { - "type": "git", - "url": "https://github.com/php-fig/cache.git", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf", - "shasum": "" - }, - "require": { - "php": ">=8.0.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "https://www.php-fig.org/" - } - ], - "description": "Common interface for caching libraries", - "keywords": [ - "cache", - "psr", - "psr-6" - ], - "support": { - "source": "https://github.com/php-fig/cache/tree/3.0.0" - }, - "time": "2021-02-03T23:26:27+00:00" - }, { "name": "psr/clock", "version": "1.0.0", @@ -8930,16 +8583,16 @@ }, { "name": "spatie/laravel-medialibrary", - "version": "11.4.3", + "version": "11.4.4", "source": { "type": "git", "url": "https://github.com/spatie/laravel-medialibrary.git", - "reference": "d36b8f2b39811e0269b213df6b166890304360f4" + "reference": "bb29cacfe209856db304adc6c5ba8c05ff7a8802" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/d36b8f2b39811e0269b213df6b166890304360f4", - "reference": "d36b8f2b39811e0269b213df6b166890304360f4", + "url": "https://api.github.com/repos/spatie/laravel-medialibrary/zipball/bb29cacfe209856db304adc6c5ba8c05ff7a8802", + "reference": "bb29cacfe209856db304adc6c5ba8c05ff7a8802", "shasum": "" }, "require": { @@ -9022,7 +8675,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-medialibrary/issues", - "source": "https://github.com/spatie/laravel-medialibrary/tree/11.4.3" + "source": "https://github.com/spatie/laravel-medialibrary/tree/11.4.4" }, "funding": [ { @@ -9034,7 +8687,7 @@ "type": "github" } ], - "time": "2024-02-20T15:26:46+00:00" + "time": "2024-02-22T09:39:31+00:00" }, { "name": "spatie/laravel-package-tools", diff --git a/packages/actions/docs/07-prebuilt-actions/08-import.md b/packages/actions/docs/07-prebuilt-actions/08-import.md index 0486e60069..ff64226ab7 100644 --- a/packages/actions/docs/07-prebuilt-actions/08-import.md +++ b/packages/actions/docs/07-prebuilt-actions/08-import.md @@ -67,8 +67,6 @@ If you'd like to save time, Filament can automatically generate the [columns](#d php artisan make:filament-importer Product --generate ``` -> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the columns for your importer if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808). - ## Defining importer columns To define the columns that can be imported, you need to override the `getColumns()` method on your importer class, returning an array of `ImportColumn` objects: diff --git a/packages/actions/docs/07-prebuilt-actions/09-export.md b/packages/actions/docs/07-prebuilt-actions/09-export.md index 38f7cdbe14..08f1c4980d 100644 --- a/packages/actions/docs/07-prebuilt-actions/09-export.md +++ b/packages/actions/docs/07-prebuilt-actions/09-export.md @@ -82,8 +82,6 @@ If you'd like to save time, Filament can automatically generate the [columns](#d php artisan make:filament-exporter Product --generate ``` -> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the columns for your exporter if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808). - ## Defining exporter columns To define the columns that can be exported, you need to override the `getColumns()` method on your exporter class, returning an array of `ExportColumn` objects: diff --git a/packages/actions/src/Commands/Concerns/CanGenerateExporterColumns.php b/packages/actions/src/Commands/Concerns/CanGenerateExporterColumns.php index abb6a4a754..b4eed679c5 100644 --- a/packages/actions/src/Commands/Concerns/CanGenerateExporterColumns.php +++ b/packages/actions/src/Commands/Concerns/CanGenerateExporterColumns.php @@ -14,17 +14,12 @@ trait CanGenerateExporterColumns return '//'; } + $schema = $this->getModelSchema($model); $table = $this->getModelTable($model); - if (blank($table)) { - return '//'; - } - $columns = []; - foreach ($table->getColumns() as $column) { - $columnName = $column->getName(); - + foreach ($schema->getColumnListing($table) as $columnName) { if (str($columnName)->endsWith([ '_token', ])) { @@ -38,10 +33,10 @@ trait CanGenerateExporterColumns } if (str($columnName)->endsWith('_id')) { - $guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model); + $guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model); if (filled($guessedRelationshipName)) { - $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($column, app($model)->{$guessedRelationshipName}()->getModel()::class); + $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($columnName, app($model)->{$guessedRelationshipName}()->getModel()::class); $columnName = "{$guessedRelationshipName}.{$guessedRelationshipTitleColumnName}"; } diff --git a/packages/actions/src/Commands/Concerns/CanGenerateImporterColumns.php b/packages/actions/src/Commands/Concerns/CanGenerateImporterColumns.php index be0560fc65..c3b6f4c6bb 100644 --- a/packages/actions/src/Commands/Concerns/CanGenerateImporterColumns.php +++ b/packages/actions/src/Commands/Concerns/CanGenerateImporterColumns.php @@ -2,7 +2,6 @@ namespace Filament\Actions\Commands\Concerns; -use Doctrine\DBAL\Types; use Illuminate\Support\Str; trait CanGenerateImporterColumns @@ -15,20 +14,17 @@ trait CanGenerateImporterColumns return '//'; } + $schema = $this->getModelSchema($model); $table = $this->getModelTable($model); - if (blank($table)) { - return '//'; - } - $columns = []; - foreach ($table->getColumns() as $column) { - if ($column->getAutoincrement()) { + foreach ($schema->getColumns($table) as $column) { + if ($column['auto_increment']) { continue; } - $columnName = $column->getName(); + $columnName = $column['name']; if (str($columnName)->is([ app($model)->getKeyName(), @@ -50,7 +46,7 @@ trait CanGenerateImporterColumns $columnData['label'] = [Str::upper($columnName)]; } - if ($column->getNotnull()) { + if (! $column['nullable']) { $columnData['rules'][0][] = 'required'; $columnData['requiredMapping'] = []; } @@ -59,44 +55,39 @@ trait CanGenerateImporterColumns $columnData['rules'][0][] = 'email'; } + $type = $this->parseColumnType($column); + if ( str($columnName)->endsWith('_id') && - filled($guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model)) + filled($guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model)) ) { $columnName = $guessedRelationshipName; $columnData['relationship'] = []; - } elseif (in_array($column->getType()::class, [ - Types\BooleanType::class, + } elseif (in_array($type['name'], [ + 'boolean', ])) { $columnData['rules'][0][] = 'boolean'; $columnData['boolean'] = []; - } elseif (in_array($column->getType()::class, [ - Types\DateImmutableType::class, - Types\DateType::class, + } elseif (in_array($type['name'], [ + 'date', ])) { $columnData['rules'][0][] = 'date'; - } elseif (in_array($column->getType()::class, [ - Types\DateTimeImmutableType::class, - Types\DateTimeType::class, - Types\DateTimeTzImmutableType::class, - Types\DateTimeTzType::class, + } elseif (in_array($type['name'], [ + 'datetime', + 'timestamp', ])) { $columnData['rules'][0][] = 'datetime'; - } elseif (in_array($column->getType()::class, [ - Types\IntegerType::class, - Types\SmallIntType::class, - Types\BigIntType::class, + } elseif (in_array($type['name'], [ + 'integer', + 'decimal', + 'float', + 'double', + 'money', ])) { $columnData['rules'][0][] = 'integer'; $columnData['numeric'] = []; - } elseif (in_array($column->getType()::class, [ - Types\DecimalType::class, - Types\FloatType::class, - ])) { - $columnData['rules'][0][] = 'integer'; - $columnData['numeric'] = []; - } elseif ($length = $column->getLength()) { - $columnData['rules'][0][] = "max:{$length}"; + } elseif (isset($type['length'])) { + $columnData['rules'][0][] = "max:{$type['length']}"; } // Move rules to the end of the column definition. diff --git a/packages/forms/docs/08-adding-a-form-to-a-livewire-component.md b/packages/forms/docs/08-adding-a-form-to-a-livewire-component.md index e819e15249..6d3a9cb0e2 100644 --- a/packages/forms/docs/08-adding-a-form-to-a-livewire-component.md +++ b/packages/forms/docs/08-adding-a-form-to-a-livewire-component.md @@ -329,5 +329,3 @@ Filament is also able to guess which form fields you want in the schema, based o ```bash php artisan make:livewire-form Products/CreateProduct --generate ``` - -> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the schema for your table if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808). diff --git a/packages/forms/src/Commands/Concerns/CanGenerateForms.php b/packages/forms/src/Commands/Concerns/CanGenerateForms.php index 0e63c78848..e888f2eb40 100644 --- a/packages/forms/src/Commands/Concerns/CanGenerateForms.php +++ b/packages/forms/src/Commands/Concerns/CanGenerateForms.php @@ -2,7 +2,6 @@ namespace Filament\Forms\Commands\Concerns; -use Doctrine\DBAL\Types; use Filament\Forms; use Illuminate\Support\Str; @@ -16,20 +15,17 @@ trait CanGenerateForms return '//'; } + $schema = $this->getModelSchema($model); $table = $this->getModelTable($model); - if (blank($table)) { - return '//'; - } - $components = []; - foreach ($table->getColumns() as $column) { - if ($column->getAutoincrement()) { + foreach ($schema->getColumns($table) as $column) { + if ($column['auto_increment']) { continue; } - $columnName = $column->getName(); + $columnName = $column['name']; if (str($columnName)->is([ app($model)->getKeyName(), @@ -41,22 +37,24 @@ trait CanGenerateForms continue; } + $type = $this->parseColumnType($column); + $componentData = []; $componentData['type'] = match (true) { - $column->getType()::class === Types\BooleanType::class => Forms\Components\Toggle::class, - in_array($column->getType()::class, [Types\DateImmutableType::class, Types\DateType::class]) => Forms\Components\DatePicker::class, - in_array($column->getType()::class, [Types\DateTimeImmutableType::class, Types\DateTimeType::class, Types\DateTimeTzImmutableType::class, Types\DateTimeTzType::class]) => Forms\Components\DateTimePicker::class, - $column->getType()::class === Types\TextType::class => Forms\Components\Textarea::class, + $type['name'] === 'boolean' => Forms\Components\Toggle::class, + $type['name'] === 'date' => Forms\Components\DatePicker::class, + in_array($type['name'], ['datetime', 'timestamp']) => Forms\Components\DateTimePicker::class, + $type['name'] === 'text' => Forms\Components\Textarea::class, $columnName === 'image', str($columnName)->startsWith('image_'), str($columnName)->contains('_image_'), str($columnName)->endsWith('_image') => Forms\Components\FileUpload::class, default => Forms\Components\TextInput::class, }; if (str($columnName)->endsWith('_id')) { - $guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model); + $guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model); if (filled($guessedRelationshipName)) { - $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($column, app($model)->{$guessedRelationshipName}()->getModel()::class); + $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($columnName, app($model)->{$guessedRelationshipName}()->getModel()::class); $componentData['type'] = Forms\Components\Select::class; $componentData['relationship'] = [$guessedRelationshipName, $guessedRelationshipTitleColumnName]; @@ -89,40 +87,40 @@ trait CanGenerateForms $componentData['image'] = []; } - if ($column->getNotnull()) { + if (! $column['nullable']) { $componentData['required'] = []; } - if (in_array($column->getType()::class, [ - Types\BigIntType::class, - Types\DecimalType::class, - Types\FloatType::class, - Types\IntegerType::class, - Types\SmallIntType::class, + if (in_array($type['name'], [ + 'integer', + 'decimal', + 'float', + 'double', + 'money', ])) { if ($componentData['type'] === Forms\Components\TextInput::class) { $componentData['numeric'] = []; } - if (filled($column->getDefault())) { - $componentData['default'] = [$column->getDefault()]; + if (filled($column['default'])) { + $componentData['default'] = [$this->parseDefaultExpression($column, $model)]; } if (in_array($columnName, [ 'cost', 'money', 'price', - ])) { + ]) || $type['name'] === 'money') { $componentData['prefix'] = ['$']; } } elseif (in_array($componentData['type'], [ Forms\Components\TextInput::class, Forms\Components\Textarea::class, - ]) && ($length = $column->getLength())) { - $componentData['maxLength'] = [$length]; + ]) && isset($type['length'])) { + $componentData['maxLength'] = [$type['length']]; - if (filled($column->getDefault())) { - $componentData['default'] = [$column->getDefault()]; + if (filled($column['default'])) { + $componentData['default'] = [$this->parseDefaultExpression($column, $model)]; } } diff --git a/packages/panels/docs/03-resources/01-getting-started.md b/packages/panels/docs/03-resources/01-getting-started.md index 9e90d29f4b..15321c9bae 100644 --- a/packages/panels/docs/03-resources/01-getting-started.md +++ b/packages/panels/docs/03-resources/01-getting-started.md @@ -60,8 +60,6 @@ If you'd like to save time, Filament can automatically generate the [form](#reso php artisan make:filament-resource Customer --generate ``` -> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the schema for your resource if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808). - ### Handling soft deletes By default, you will not be able to interact with deleted records in the app. If you'd like to add functionality to restore, force delete and filter trashed records in your resource, use the `--soft-deletes` flag when generating the resource: diff --git a/packages/support/composer.json b/packages/support/composer.json index 2b55d9a6ba..7719b6fb66 100644 --- a/packages/support/composer.json +++ b/packages/support/composer.json @@ -10,7 +10,6 @@ "require": { "php": "^8.1", "blade-ui-kit/blade-heroicons": "^2.2.1", - "doctrine/dbal": "^3.2", "ext-intl": "*", "illuminate/contracts": "^10.45|^11.0", "illuminate/support": "^10.45|^11.0", diff --git a/packages/support/src/Commands/Concerns/CanReadModelSchemas.php b/packages/support/src/Commands/Concerns/CanReadModelSchemas.php index 241d1ab1b5..c3df34f1cd 100644 --- a/packages/support/src/Commands/Concerns/CanReadModelSchemas.php +++ b/packages/support/src/Commands/Concerns/CanReadModelSchemas.php @@ -2,15 +2,12 @@ namespace Filament\Support\Commands\Concerns; -use Doctrine\DBAL\Schema\AbstractAsset; -use Doctrine\DBAL\Schema\Table; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Facades\Schema; use Illuminate\Support\Str; use ReflectionClass; use ReflectionException; -use Throwable; trait CanReadModelSchemas { @@ -23,29 +20,24 @@ trait CanReadModelSchemas return $model; } - protected function getModelTable(string $model): ?Table + protected function getModelSchema(string $model): Schema { - $modelClass = $model; - $model = app($model); - - try { - return $model - ->getConnection() - ->getDoctrineSchemaManager() - ->listTableDetails($model->getTable()); - } catch (Throwable $exception) { - $this->components->warn("Unable to read table schema for model [{$modelClass}]: {$exception->getMessage()}"); - - return null; - } + return app($model) + ->getConnection() + ->getSchemaBuilder(); } - protected function guessBelongsToRelationshipName(AbstractAsset $column, string $model): ?string + protected function getModelTable(string $model): string + { + return app($model)->getTable(); + } + + protected function guessBelongsToRelationshipName(string $column, string $model): ?string { /** @var Model $modelInstance */ $modelInstance = app($model); $modelInstanceReflection = new ReflectionClass($modelInstance); - $guessedRelationshipName = str($column->getName())->beforeLast('_id'); + $guessedRelationshipName = str($column)->beforeLast('_id'); $hasRelationship = $modelInstanceReflection->hasMethod($guessedRelationshipName); if (! $hasRelationship) { @@ -74,9 +66,9 @@ trait CanReadModelSchemas return $guessedRelationshipName; } - protected function guessBelongsToRelationshipTableName(AbstractAsset $column): ?string + protected function guessBelongsToRelationshipTableName(string $column): ?string { - $tableName = str($column->getName())->beforeLast('_id'); + $tableName = str($column)->beforeLast('_id'); if (Schema::hasTable(Str::plural($tableName))) { return Str::plural($tableName); @@ -89,15 +81,12 @@ trait CanReadModelSchemas return $tableName; } - protected function guessBelongsToRelationshipTitleColumnName(AbstractAsset $column, string $model): string + protected function guessBelongsToRelationshipTitleColumnName(string $column, string $model): string { - $schema = $this->getModelTable($model); + $schema = $this->getModelSchema($model); + $table = $this->getModelTable($model); - if ($schema === null) { - return 'id'; - } - - $columns = collect(array_keys($schema->getColumns())); + $columns = collect($schema->getColumnListing($table)); if ($columns->contains('name')) { return 'name'; @@ -107,6 +96,113 @@ trait CanReadModelSchemas return 'title'; } - return $schema->getPrimaryKey()->getColumns()[0]; + return collect($schema->getIndexes($table))->firstWhere('primary')['columns'][0] ?? 'id'; + } + + /** + * @param array $column + * @return array + */ + protected function parseColumnType(array $column): array + { + $type = match ($column['type']) { + 'tinyint(1)', 'bit' => 'boolean', + 'varchar(max)', 'nvarchar(max)' => 'text', + default => null, + }; + + $type ??= match ($column['type_name']) { + 'boolean', 'bool' => 'boolean', + 'char', 'bpchar', 'nchar' => 'char', + 'varchar', 'nvarchar' => 'string', + 'integer', 'int', 'int4', 'tinyint', 'smallint', 'int2', 'mediumint', 'bigint', 'int8' => 'integer', + 'decimal', 'numeric' => 'decimal', + 'float', 'real', 'float4' => 'float', + 'double', 'float8' => 'double', + 'money', 'smallmoney' => 'money', + 'date' => 'date', + 'time', 'timetz' => 'time', + 'datetime', 'datetime2', 'smalldatetime', 'datetimeoffset' => 'datetime', + 'timestamp', 'timestamptz' => 'timestamp', + 'text', 'tinytext', 'longtext', 'mediumtext', 'ntext' => 'text', + 'json', 'jsonb' => 'json', + default => $column['type_name'], + }; + + $values = str_contains($column['type'], '(') + ? str_getcsv(Str::between($column['type'], '(', ')'), ',', "'") + : null; + + $values = is_null($values) ? [] : match ($type) { + 'string', 'char', 'binary', 'bit' => ['length' => (int) $values[0]], + default => [], + }; + + return array_merge(['name' => $type], array_filter($values)); + } + + /** + * @param array $column + */ + protected function parseDefaultExpression(array $column, string $model): mixed + { + $default = $column['default']; + + if (blank($default)) { + return null; + } + + $driver = app($model)->getConnection()->getDriverName(); + + if ($driver === 'mysql') { + if ($default === 'NULL' + || preg_match("/^\(.*\)$/", $default) === 1 + || str_ends_with($default, '()') + || str_starts_with(strtolower($default), 'current_timestamp')) { + return null; + } + + if (preg_match("/^'(.*)'$/", $default, $matches) === 1) { + return str_replace("''", "'", $matches[1]); + } + } + + if ($driver === 'pgsql') { + if (str_starts_with($default, 'NULL::')) { + $default = null; + } + + if (preg_match("/^['(](.*)[')]::/", $default, $matches) === 1) { + return str_replace("''", "'", $matches[1]); + } + } + + if ($driver === 'sqlsrv') { + while (preg_match('/^\((.*)\)$/', $default, $matches)) { + $default = $matches[1]; + } + + if ($default === 'NULL' + || str_ends_with($default, '()')) { + return null; + } + + if (preg_match('/^\'(.*)\'$/', $default, $matches) === 1) { + return str_replace("''", "'", $matches[1]); + } + } + + if ($driver === 'sqlite') { + if ($default === 'NULL' + || str_starts_with(strtolower($default), 'current_timestamp')) { + return null; + } + + if (preg_match('/^\'(.*)\'$/s', $default, $matches) === 1) { + return str_replace("''", "'", $matches[1]); + } + } + + return $default; } } diff --git a/packages/tables/docs/11-adding-a-table-to-a-livewire-component.md b/packages/tables/docs/11-adding-a-table-to-a-livewire-component.md index 75d4e044d6..8b225d260a 100644 --- a/packages/tables/docs/11-adding-a-table-to-a-livewire-component.md +++ b/packages/tables/docs/11-adding-a-table-to-a-livewire-component.md @@ -135,5 +135,3 @@ Filament is also able to guess which table columns you want in the table, based ```bash php artisan make:livewire-table Products/ListProducts --generate ``` - -> If your table contains ENUM columns, the `doctrine/dbal` package we use is unable to scan your table and will crash. Hence, Filament is unable to generate the schema for your table if it contains an ENUM column. Read more about this issue [here](https://github.com/doctrine/dbal/issues/3819#issuecomment-573419808). diff --git a/packages/tables/src/Commands/Concerns/CanGenerateTables.php b/packages/tables/src/Commands/Concerns/CanGenerateTables.php index b4ea2f34a5..113fbf68ab 100644 --- a/packages/tables/src/Commands/Concerns/CanGenerateTables.php +++ b/packages/tables/src/Commands/Concerns/CanGenerateTables.php @@ -2,7 +2,6 @@ namespace Filament\Tables\Commands\Concerns; -use Doctrine\DBAL\Types; use Filament\Tables; use Illuminate\Support\Str; @@ -16,27 +15,26 @@ trait CanGenerateTables return '//'; } + $schema = $this->getModelSchema($model); $table = $this->getModelTable($model); - if (blank($table)) { - return '//'; - } - $columns = []; - foreach ($table->getColumns() as $column) { - if ($column->getAutoincrement()) { + foreach ($schema->getColumns($table) as $column) { + if ($column['auto_increment']) { continue; } - if (in_array($column->getType()::class, [ - Types\JsonType::class, - Types\TextType::class, + $type = $this->parseColumnType($column); + + if (in_array($type['name'], [ + 'json', + 'text', ])) { continue; } - $columnName = $column->getName(); + $columnName = $column['name']; if (str($columnName)->endsWith([ '_token', @@ -51,10 +49,10 @@ trait CanGenerateTables } if (str($columnName)->endsWith('_id')) { - $guessedRelationshipName = $this->guessBelongsToRelationshipName($column, $model); + $guessedRelationshipName = $this->guessBelongsToRelationshipName($columnName, $model); if (filled($guessedRelationshipName)) { - $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($column, app($model)->{$guessedRelationshipName}()->getModel()::class); + $guessedRelationshipTitleColumnName = $this->guessBelongsToRelationshipTitleColumnName($columnName, app($model)->{$guessedRelationshipName}()->getModel()::class); $columnName = "{$guessedRelationshipName}.{$guessedRelationshipTitleColumnName}"; } @@ -70,7 +68,7 @@ trait CanGenerateTables $columnData['label'] = [Str::upper($columnName)]; } - if ($column->getType() instanceof Types\BooleanType) { + if ($type['name'] === 'boolean') { $columnData['type'] = Tables\Columns\IconColumn::class; $columnData['boolean'] = []; } else { @@ -79,42 +77,40 @@ trait CanGenerateTables default => Tables\Columns\TextColumn::class, }; - if (in_array($column->getType()::class, [ - Types\StringType::class, + if (in_array($type['name'], [ + 'string', + 'char', ]) && ($columnData['type'] === Tables\Columns\TextColumn::class)) { $columnData['searchable'] = []; } - if (in_array($column->getType()::class, [ - Types\DateImmutableType::class, - Types\DateType::class, + if (in_array($type['name'], [ + 'date', ])) { $columnData['date'] = []; $columnData['sortable'] = []; } - if (in_array($column->getType()::class, [ - Types\DateTimeImmutableType::class, - Types\DateTimeType::class, - Types\DateTimeTzImmutableType::class, - Types\DateTimeTzType::class, + if (in_array($type['name'], [ + 'datetime', + 'timestamp', ])) { $columnData['dateTime'] = []; $columnData['sortable'] = []; } - if (in_array($column->getType()::class, [ - Types\BigIntType::class, - Types\DecimalType::class, - Types\FloatType::class, - Types\IntegerType::class, - Types\SmallIntType::class, + if (in_array($type['name'], [ + 'integer', + 'decimal', + 'float', + 'double', + 'money', ])) { $columnData[in_array($columnName, [ 'cost', 'money', 'price', - ]) ? 'money' : 'numeric'] = []; + ]) || $type['name'] === 'money' ? 'money' : 'numeric'] = []; $columnData['sortable'] = []; } }