diff --git a/docs/14-upgrade-guide.md b/docs/14-upgrade-guide.md
index d923bcc3a58..13a8784c5be 100644
--- a/docs/14-upgrade-guide.md
+++ b/docs/14-upgrade-guide.md
@@ -20,7 +20,7 @@ import Aside from "@components/Aside.astro"
Some plugins you're using may not be available in v5 just yet. You could temporarily remove them from your `composer.json` file until they've been upgraded, replace them with a similar plugins that are v5-compatible, wait for the plugins to be upgraded before upgrading your app, or even write PRs to help the authors upgrade them.
-You can upgrade your Filament app by running the automated upgrade script. This script will check your codebase for compatibility issues, and suggest Composer update commands to run:
+The first step to upgrade your Filament app is to run the automated upgrade script. This script will automatically upgrade your application to the latest version of Filament and make changes to your code, which handles breaking changes:
```bash
composer require filament/upgrade:"^5.0" -W --dev
@@ -46,6 +46,8 @@ composer update
```
+Make sure to carefully follow the instructions, and review the changes made by the script. You may need to make some manual changes to your code afterwards, but the script should handle most of the repetitive work for you.
+
You can now `composer remove filament/upgrade --dev` as you don't need it anymore.
## Upgrading Livewire
diff --git a/packages/panels/src/Resources/Resource/Concerns/HasAuthorization.php b/packages/panels/src/Resources/Resource/Concerns/HasAuthorization.php
index fc0c569f6c5..6713992a12a 100644
--- a/packages/panels/src/Resources/Resource/Concerns/HasAuthorization.php
+++ b/packages/panels/src/Resources/Resource/Concerns/HasAuthorization.php
@@ -5,6 +5,7 @@
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Auth\Access\Response;
use Illuminate\Database\Eloquent\Model;
+use UnitEnum;
use function Filament\get_authorization_response;
@@ -19,7 +20,7 @@ public static function canAccess(): bool
return static::canViewAny();
}
- public static function getAuthorizationResponse(string $action, ?Model $record = null): Response
+ public static function getAuthorizationResponse(string | UnitEnum $action, ?Model $record = null): Response
{
if (static::shouldSkipAuthorization()) {
return Response::allow();
@@ -28,7 +29,7 @@ public static function getAuthorizationResponse(string $action, ?Model $record =
return get_authorization_response($action, $record ?? static::getModel(), static::shouldCheckPolicyExistence());
}
- public static function can(string $action, ?Model $record = null): bool
+ public static function can(string | UnitEnum $action, ?Model $record = null): bool
{
return static::getAuthorizationResponse($action, $record)->allowed();
}
@@ -36,7 +37,7 @@ public static function can(string $action, ?Model $record = null): bool
/**
* @throws AuthorizationException
*/
- public static function authorize(string $action, ?Model $record = null): ?Response
+ public static function authorize(string | UnitEnum $action, ?Model $record = null): ?Response
{
return static::getAuthorizationResponse($action, $record)->authorize();
}
diff --git a/packages/upgrade/bin/filament-v5 b/packages/upgrade/bin/filament-v5
index 847c88fa302..81446b60568 100755
--- a/packages/upgrade/bin/filament-v5
+++ b/packages/upgrade/bin/filament-v5
@@ -17,8 +17,24 @@ render(<<<'HTML'
+ This script will attempt to handle breaking changes for you, and will guide you through any manual steps that may be required.
+
+
+
If you have any questions, please reach out to us on Discord or GitHub.
+
+
+ To begin, please ensure that you are using a version control system such as Git.
+
+
+
+ We will make changes directly to your files, and you will need to be able to revert them if something goes wrong.
+
+
+
+ Please commit any changes you have made to your project before continuing.
+
@@ -27,6 +43,45 @@ HTML);
require __DIR__ . '/../src/check-compatibility.php';
+$directories = $argv[1] ?? ask(<<
+ Please provide a comma-separated list of directories containing Filament code to upgrade (e.g. app, app-modules, src).
+
+
+
+
+ You can skip this if you have a normal Laravel app structure:
+
+
+ HTML) ?: 'app';
+
+render(<<
+ Starting upgrade...
+
+HTML);
+
+$rectorScriptPath = implode(DIRECTORY_SEPARATOR, ['vendor', 'bin', 'rector']);
+
+foreach (explode(',', $directories) as $directory) {
+ $directory = trim($directory);
+ $directory = trim($directory, DIRECTORY_SEPARATOR);
+
+ render(<<
+ Start processing /{$directory} to fix code affected by breaking changes.
+
+ HTML);
+
+ exec("{$rectorScriptPath} process {$directory} --config vendor/filament/upgrade/src/rector.php --clear-cache");
+
+ render(<<
+ Finished processing /{$directory}.
+
+ HTML);
+}
+
$requireCommands = [];
foreach (json_decode(file_get_contents('composer.json'), true)['require'] as $package => $version) {
diff --git a/packages/upgrade/composer.json b/packages/upgrade/composer.json
index e9829ceb3e1..77d8e4f2550 100644
--- a/packages/upgrade/composer.json
+++ b/packages/upgrade/composer.json
@@ -9,7 +9,8 @@
},
"require": {
"php": "^8.2",
- "nunomaduro/termwind": "^2.0"
+ "nunomaduro/termwind": "^2.0",
+ "rector/rector": "^2.0"
},
"autoload": {
"psr-4": {
diff --git a/packages/upgrade/src/Rector/SimpleMethodChangesRector.php b/packages/upgrade/src/Rector/SimpleMethodChangesRector.php
new file mode 100644
index 00000000000..ae11ce79dcb
--- /dev/null
+++ b/packages/upgrade/src/Rector/SimpleMethodChangesRector.php
@@ -0,0 +1,116 @@
+,
+ * changes: array,
+ * }>
+ */
+ public function getChanges(): array
+ {
+ $addUnitEnumToAuthorizationActionParamModifier = static function (ClassMethod $node): void {
+ foreach ($node->getParams() as $param) {
+ if ($param->var->name !== 'action') {
+ continue;
+ }
+
+ $param->type = new UnionType([new Identifier('string'), new FullyQualified('UnitEnum')]);
+ }
+ };
+
+ return [
+ [
+ 'class' => [
+ Resource::class,
+ ],
+ 'changes' => [
+ 'getAuthorizationResponse' => $addUnitEnumToAuthorizationActionParamModifier,
+ 'can' => $addUnitEnumToAuthorizationActionParamModifier,
+ 'authorize' => $addUnitEnumToAuthorizationActionParamModifier,
+ ],
+ ],
+ ];
+ }
+
+ public function getNodeTypes(): array
+ {
+ return [Class_::class, Enum_::class];
+ }
+
+ /**
+ * @param Class_ | Enum_ $node
+ */
+ public function refactor(Node $node): ?Node
+ {
+ $touched = false;
+
+ foreach ($this->getChanges() as $change) {
+ if (! $this->isClassMatchingChange($node, $change)) {
+ continue;
+ }
+
+ foreach ($change['changes'] as $methodName => $modifier) {
+ foreach ($node->getMethods() as $method) {
+ if (! $this->isName($method, $methodName)) {
+ continue;
+ }
+
+ $modifier($method);
+
+ $touched = true;
+ }
+ }
+ }
+
+ return $touched ? $node : null;
+ }
+
+ /**
+ * @param array{
+ * class: class-string | array,
+ * } $change
+ */
+ public function isClassMatchingChange(Class_ | Enum_ $class, array $change): bool
+ {
+ $classes = is_array($change['class']) ?
+ $change['class'] :
+ [$change['class']];
+
+ $classes = array_map(fn (string $class): string => ltrim($class, '\\'), $classes);
+
+ foreach ($classes as $classToCheck) {
+ if ($class instanceof Enum_) {
+ foreach ($class->implements as $enumInterface) {
+ if ($enumInterface->toString() === $classToCheck) {
+ return true;
+ }
+ }
+
+ continue;
+ }
+
+ if ($this->isObjectType($class, new ObjectType($classToCheck))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/packages/upgrade/src/rector.php b/packages/upgrade/src/rector.php
new file mode 100644
index 00000000000..6d278beed42
--- /dev/null
+++ b/packages/upgrade/src/rector.php
@@ -0,0 +1,10 @@
+rules([
+ Rector\SimpleMethodChangesRector::class,
+ ]);
+};