diff --git a/.github/workflows/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..1f45be1 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,26 @@ +name: PHPStan + +on: + push: + branches: + - main + pull_request: + +jobs: + phpstan: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.3' + coverage: none + + - name: Install Composer dependencies + run: composer install --no-interaction --prefer-dist --no-progress + + - name: Run Larastan + run: composer analyse diff --git a/.lando.yml b/.lando.yml index c1a78f1..262f677 100644 --- a/.lando.yml +++ b/.lando.yml @@ -32,6 +32,11 @@ tooling: description: Run Duster fix|lint cmd: ./vendor/bin/duster dir: /app + analyse: + service: appserver + description: Run Larastan static analysis + cmd: composer analyse + dir: /app art: service: appserver description: Run an Artisan command diff --git a/app/Console/Commands/CreateUser.php b/app/Console/Commands/CreateUser.php index cb2b4e3..b49fbe4 100644 --- a/app/Console/Commands/CreateUser.php +++ b/app/Console/Commands/CreateUser.php @@ -7,6 +7,7 @@ use App\Models\User; use Exception; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Hash; use function Laravel\Prompts\password; use function Laravel\Prompts\select; @@ -31,7 +32,7 @@ class CreateUser extends Command /** * Execute the console command. */ - public function handle() + public function handle(): int { $name = text(label: 'What is the name of the user?', required: true); $email = text( @@ -73,9 +74,11 @@ public function handle() } catch (Exception $e) { $this->error("Failed to create user: {$e->getMessage()}"); - return; + return Command::FAILURE; } $this->info("User: {$user->name} with role {$role} created successfully."); + + return Command::SUCCESS; } } diff --git a/app/Console/Commands/TestMailDriver.php b/app/Console/Commands/TestMailDriver.php index 75901e6..4b961c8 100644 --- a/app/Console/Commands/TestMailDriver.php +++ b/app/Console/Commands/TestMailDriver.php @@ -52,6 +52,9 @@ public function handle(): void $this->info('Test email sent'); } + /** + * @return array + */ protected function promptForMissingArgumentsUsing(): array { return [ diff --git a/app/Enums/NavigationGroupsEnum.php b/app/Enums/NavigationGroupsEnum.php index e953c60..41830f4 100644 --- a/app/Enums/NavigationGroupsEnum.php +++ b/app/Enums/NavigationGroupsEnum.php @@ -8,7 +8,7 @@ enum NavigationGroupsEnum: string implements HasLabel { case ADMIN = 'admin'; - public function getLabel(): ?string + public function getLabel(): string { return match ($this) { self::ADMIN => __('Organization'), diff --git a/app/Enums/RolesEnum.php b/app/Enums/RolesEnum.php index 32b131e..05442c4 100644 --- a/app/Enums/RolesEnum.php +++ b/app/Enums/RolesEnum.php @@ -11,7 +11,7 @@ enum RolesEnum: string implements HasColor, HasLabel case ADMIN = 'admin'; case EDITOR = 'editor'; - public function getLabel(): ?string + public function getLabel(): string { return match ($this) { self::SUPER_ADMIN => __('Super Admin'), @@ -20,7 +20,7 @@ public function getLabel(): ?string }; } - public function getColor(): string|array|null + public function getColor(): string { return match ($this) { self::SUPER_ADMIN => 'primary', diff --git a/app/Filament/Pages/Auth/EditProfile.php b/app/Filament/Pages/Auth/EditProfile.php index 0890d52..4f3fbe3 100644 --- a/app/Filament/Pages/Auth/EditProfile.php +++ b/app/Filament/Pages/Auth/EditProfile.php @@ -6,6 +6,7 @@ use App\Models\Role; use Filament\Auth\Pages\EditProfile as EditProfileBase; use Filament\Forms\Components\Select; +use Filament\Forms\Components\TextInput; use Filament\Schemas\Components\Tabs; use Filament\Schemas\Components\Tabs\Tab; use Filament\Schemas\Schema; @@ -33,21 +34,33 @@ public function getBreadcrumbs(): array public function form(Schema $schema): Schema { + /** @var TextInput $nameFormComponent */ + $nameFormComponent = $this->getNameFormComponent(); + + /** @var TextInput $emailFormComponent */ + $emailFormComponent = $this->getEmailFormComponent(); + + /** @var TextInput $passwordFormComponent */ + $passwordFormComponent = $this->getPasswordFormComponent(); + + /** @var TextInput $passwordConfirmationFormComponent */ + $passwordConfirmationFormComponent = $this->getPasswordConfirmationFormComponent(); + return $schema ->components([ Tabs::make('Tabs') ->tabs([ Tab::make(__('Account Details')) ->schema([ - $this->getNameFormComponent() + $nameFormComponent ->helperText(__('Your full name, used for display purposes')), - $this->getEmailFormComponent() + $emailFormComponent ->helperText(__('Your email address, used for notifications and account recovery')) ->prefixIcon('phosphor-envelope-simple'), Select::make('roles') ->label(__('Role')) ->relationship('roles', 'name') - ->getOptionLabelFromRecordUsing(fn (Role $record) => $record?->name?->getLabel()) + ->getOptionLabelFromRecordUsing(fn (Role $record): string => $record->name->getLabel()) ->prefixIcon('phosphor-shield-check') ->required() ->searchable() @@ -55,9 +68,9 @@ public function form(Schema $schema): Schema ]), Tab::make(__('Security')) ->schema([ - $this->getPasswordFormComponent() + $passwordFormComponent ->label(__('New Password')), - $this->getPasswordConfirmationFormComponent() + $passwordConfirmationFormComponent ->label(__('Confirm New Password')) ->helperText(__('Verify your new password')), $this->getCurrentPasswordFormComponent(), diff --git a/app/Filament/Resources/Users/Actions/InviteUserAction.php b/app/Filament/Resources/Users/Actions/InviteUserAction.php index 398b57b..cd6a8d7 100644 --- a/app/Filament/Resources/Users/Actions/InviteUserAction.php +++ b/app/Filament/Resources/Users/Actions/InviteUserAction.php @@ -58,6 +58,9 @@ public static function make(string $name): Action ->action(fn (array $data) => InviteUserAction::handle($data)); } + /** + * @param array{name: string, email: string, role: string} $data + */ public static function handle(array $data): void { $validator = Validator::make($data, [ diff --git a/app/Filament/Resources/Users/Schemas/UserForm.php b/app/Filament/Resources/Users/Schemas/UserForm.php index 091d106..7860e3a 100644 --- a/app/Filament/Resources/Users/Schemas/UserForm.php +++ b/app/Filament/Resources/Users/Schemas/UserForm.php @@ -35,7 +35,7 @@ public static function configure(Schema $schema): Schema Select::make('roles') ->label(__('Role')) ->relationship('roles', 'name') - ->getOptionLabelFromRecordUsing(fn (Role $record) => $record?->name?->getLabel()) + ->getOptionLabelFromRecordUsing(fn (Role $record): string => $record->name->getLabel()) ->prefixIcon('phosphor-shield-check') ->required() ->searchable() diff --git a/app/Models/Role.php b/app/Models/Role.php index de45342..beece16 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -5,6 +5,9 @@ use App\Enums\RolesEnum; use Spatie\Permission\Models\Role as SpatieRole; +/** + * @property RolesEnum $name + */ class Role extends SpatieRole { protected function casts(): array diff --git a/app/Models/User.php b/app/Models/User.php index 0d9d915..4d248dc 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -6,6 +6,7 @@ use App\Enums\RolesEnum; use App\Observers\UserObserver; +use Database\Factories\UserFactory; use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthentication; use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthenticationRecovery; use Filament\Facades\Filament; @@ -25,9 +26,7 @@ class User extends Authenticatable implements FilamentUser, HasAppAuthentication use HasFactory, HasRoles, Notifiable; /** - * The attributes that are mass assignable. - * - * @var array + * @var list */ protected $fillable = [ 'name', @@ -37,9 +36,7 @@ class User extends Authenticatable implements FilamentUser, HasAppAuthentication ]; /** - * The attributes that should be hidden for serialization. - * - * @var array + * @var list */ protected $hidden = [ 'password', diff --git a/composer.json b/composer.json index 0792205..639ece0 100644 --- a/composer.json +++ b/composer.json @@ -23,6 +23,7 @@ "barryvdh/laravel-debugbar": "^3.14", "fakerphp/faker": "^1.23", "filament/upgrade": "^4.0", + "larastan/larastan": "^3.0", "laravel/boost": "^1.0", "laravel/pail": "^1.1", "laravel/pint": "^1.13", @@ -62,6 +63,9 @@ "@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"", "@php artisan migrate --graceful --ansi" ], + "analyse": [ + "@php vendor/bin/phpstan analyse --memory-limit=2G" + ], "dev": [ "Composer\\Config::disableProcessTimeout", "npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite" diff --git a/composer.lock b/composer.lock index b39749f..69c1934 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": "77e6c447e0609e8684de93d371c747b0", + "content-hash": "2ecc1ec320d5dd26b346ab86ef299874", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -9526,6 +9526,47 @@ }, "time": "2025-04-30T06:54:44+00:00" }, + { + "name": "iamcal/sql-parser", + "version": "v0.6", + "source": { + "type": "git", + "url": "https://github.com/iamcal/SQLParser.git", + "reference": "947083e2dca211a6f12fb1beb67a01e387de9b62" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/iamcal/SQLParser/zipball/947083e2dca211a6f12fb1beb67a01e387de9b62", + "reference": "947083e2dca211a6f12fb1beb67a01e387de9b62", + "shasum": "" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^1.0", + "phpunit/phpunit": "^5|^6|^7|^8|^9" + }, + "type": "library", + "autoload": { + "psr-4": { + "iamcal\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Cal Henderson", + "email": "cal@iamcal.com" + } + ], + "description": "MySQL schema parser", + "support": { + "issues": "https://github.com/iamcal/SQLParser/issues", + "source": "https://github.com/iamcal/SQLParser/tree/v0.6" + }, + "time": "2025-03-17T16:59:46+00:00" + }, { "name": "jean85/pretty-package-versions", "version": "2.1.1", @@ -9586,6 +9627,95 @@ }, "time": "2025-03-19T14:43:43+00:00" }, + { + "name": "larastan/larastan", + "version": "v3.7.0", + "source": { + "type": "git", + "url": "https://github.com/larastan/larastan.git", + "reference": "6b7d28a2762a4b69f0f064e1e5b7358af11f04e4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/larastan/larastan/zipball/6b7d28a2762a4b69f0f064e1e5b7358af11f04e4", + "reference": "6b7d28a2762a4b69f0f064e1e5b7358af11f04e4", + "shasum": "" + }, + "require": { + "ext-json": "*", + "iamcal/sql-parser": "^0.6.0", + "illuminate/console": "^11.44.2 || ^12.4.1", + "illuminate/container": "^11.44.2 || ^12.4.1", + "illuminate/contracts": "^11.44.2 || ^12.4.1", + "illuminate/database": "^11.44.2 || ^12.4.1", + "illuminate/http": "^11.44.2 || ^12.4.1", + "illuminate/pipeline": "^11.44.2 || ^12.4.1", + "illuminate/support": "^11.44.2 || ^12.4.1", + "php": "^8.2", + "phpstan/phpstan": "^2.1.11" + }, + "require-dev": { + "doctrine/coding-standard": "^13", + "laravel/framework": "^11.44.2 || ^12.7.2", + "mockery/mockery": "^1.6.12", + "nikic/php-parser": "^5.4", + "orchestra/canvas": "^v9.2.2 || ^10.0.1", + "orchestra/testbench-core": "^9.12.0 || ^10.1", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpunit/phpunit": "^10.5.35 || ^11.5.15" + }, + "suggest": { + "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" + }, + "type": "phpstan-extension", + "extra": { + "phpstan": { + "includes": [ + "extension.neon" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Larastan\\Larastan\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Can Vural", + "email": "can9119@gmail.com" + } + ], + "description": "Larastan - Discover bugs in your code without running it. A phpstan/phpstan extension for Laravel", + "keywords": [ + "PHPStan", + "code analyse", + "code analysis", + "larastan", + "laravel", + "package", + "php", + "static analysis" + ], + "support": { + "issues": "https://github.com/larastan/larastan/issues", + "source": "https://github.com/larastan/larastan/tree/v3.7.0" + }, + "funding": [ + { + "url": "https://github.com/canvural", + "type": "github" + } + ], + "time": "2025-09-09T15:17:27+00:00" + }, { "name": "laravel/boost", "version": "v1.0.18", @@ -12733,5 +12863,5 @@ "php": "^8.3" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } diff --git a/config/settings.php b/config/settings.php index 4ffa0dd..118aded 100644 --- a/config/settings.php +++ b/config/settings.php @@ -77,7 +77,7 @@ DateTimeInterface::class => Spatie\LaravelSettings\SettingsCasts\DateTimeInterfaceCast::class, DateTimeZone::class => Spatie\LaravelSettings\SettingsCasts\DateTimeZoneCast::class, // Spatie\DataTransferObject\DataTransferObject::class => Spatie\LaravelSettings\SettingsCasts\DtoCast::class, - Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class, + // Spatie\LaravelData\Data::class => Spatie\LaravelSettings\SettingsCasts\DataCast::class, ], /* diff --git a/database/factories/UserFactory.php b/database/factories/UserFactory.php index e0c9536..1a21134 100644 --- a/database/factories/UserFactory.php +++ b/database/factories/UserFactory.php @@ -17,9 +17,13 @@ class UserFactory extends Factory protected static ?string $password; /** - * Define the model's default state. - * - * @return array + * @return array{ + * name: string, + * email: string, + * email_verified_at: \Illuminate\Support\Carbon, + * password: string, + * remember_token: string, + * } */ public function definition(): array { diff --git a/database/migrations/2022_12_14_083707_create_settings_table.php b/database/migrations/2022_12_14_083707_create_settings_table.php index 9b14b86..c497780 100644 --- a/database/migrations/2022_12_14_083707_create_settings_table.php +++ b/database/migrations/2022_12_14_083707_create_settings_table.php @@ -6,7 +6,7 @@ return new class extends Migration { - public function up() + public function up(): void { Schema::create('settings', function (Blueprint $table): void { $table->id(); diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..ce5427c --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,18 @@ +includes: + - vendor/larastan/larastan/extension.neon + - vendor/nesbot/carbon/extension.neon + +parameters: + paths: + - app/ + - config/ + - database/ + - routes/ + + level: 6 + + checkModelProperties: true + checkModelMethodVisibility: true + checkConfigTypes: true + + treatPhpDocTypesAsCertain: false