feat: integrate PHPStan, Rector and Pint code quality tools (#13)
* feat: integrate PHPStan, Rector and Pint code quality tools - Add larastan, rector, and rector-laravel dev dependencies - Configure PHPStan at max level with Larastan extension - Configure Rector with Laravel sets and code quality rules - Configure Pint with strict rules (final_class, strict_types) - Add composer scripts: lint, test:lint, test:types, test:unit - Add GitHub Actions workflow for CI on push/PR - Apply code style fixes across all files * docs: add code quality section to README
This commit is contained in:
committed by
GitHub
parent
d961b44c30
commit
f417ec23c2
55
.github/workflows/tests.yml
vendored
Normal file
55
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 8.3
|
||||
tools: composer:v2
|
||||
coverage: xdebug
|
||||
|
||||
- name: Install Dependencies
|
||||
run: composer install --no-interaction --prefer-dist --optimize-autoloader
|
||||
|
||||
- name: Copy Environment File
|
||||
run: cp .env.example .env
|
||||
|
||||
- name: Generate Application Key
|
||||
run: php artisan key:generate
|
||||
|
||||
- name: Create Database
|
||||
run: touch database/database.sqlite
|
||||
|
||||
- name: Rector Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/rector
|
||||
key: ${{ runner.os }}-rector-${{ hashFiles('composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-rector-
|
||||
- run: mkdir -p /tmp/rector
|
||||
|
||||
- name: PHPStan Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: /tmp/phpstan
|
||||
key: ${{ runner.os }}-phpstan-${{ hashFiles('composer.lock') }}
|
||||
restore-keys: ${{ runner.os }}-phpstan-
|
||||
- run: mkdir -p /tmp/phpstan
|
||||
|
||||
- name: Tests
|
||||
run: composer test
|
||||
58
README.md
58
README.md
@@ -15,6 +15,7 @@ A production-ready, API-only Laravel 12 starter kit following the 2024-2025 REST
|
||||
- **Data Objects** - Type-safe DTOs via [spatie/laravel-data](https://github.com/spatie/laravel-data)
|
||||
- **Auto Documentation** - Zero-annotation OpenAPI 3.1 via [dedoc/scramble](https://github.com/dedoc/scramble)
|
||||
- **Modern Testing** - Pest PHP with Laravel HTTP testing
|
||||
- **Code Quality** - PHPStan (max level), Rector, and Pint with strict rules
|
||||
- **Rate Limiting** - Configurable per-route rate limiters
|
||||
- **Standardized Responses** - Consistent JSON response format
|
||||
|
||||
@@ -435,15 +436,60 @@ it('requires authentication', function () {
|
||||
});
|
||||
```
|
||||
|
||||
## Code Quality
|
||||
|
||||
This kit includes strict code quality tools configured following [nunomaduro/laravel-starter-kit](https://github.com/nunomaduro/laravel-starter-kit) standards.
|
||||
|
||||
### Tools
|
||||
|
||||
| Tool | Purpose | Config |
|
||||
|------|---------|--------|
|
||||
| [PHPStan](https://phpstan.org/) + [Larastan](https://github.com/larastan/larastan) | Static analysis (level max) | `phpstan.neon` |
|
||||
| [Rector](https://getrector.com/) | Automated refactoring | `rector.php` |
|
||||
| [Pint](https://laravel.com/docs/pint) | Code style (strict rules) | `pint.json` |
|
||||
|
||||
### Composer Scripts
|
||||
|
||||
```bash
|
||||
# Apply all fixes (Rector + Pint)
|
||||
composer lint
|
||||
|
||||
# Check without fixing (CI mode)
|
||||
composer test:lint
|
||||
|
||||
# Static analysis only
|
||||
composer test:types
|
||||
|
||||
# Unit tests only
|
||||
composer test:unit
|
||||
|
||||
# Full test suite (lint + types + unit)
|
||||
composer test
|
||||
```
|
||||
|
||||
### With Docker
|
||||
|
||||
```bash
|
||||
docker compose exec app composer lint
|
||||
docker compose exec app composer test
|
||||
```
|
||||
|
||||
### Strict Rules Applied
|
||||
|
||||
- `declare(strict_types=1)` on all files
|
||||
- `final` classes by default
|
||||
- Type declarations enforced
|
||||
- Dead code removal
|
||||
- Early returns
|
||||
- Strict comparisons
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
Tests run automatically on push/PR to `main` via `.github/workflows/tests.yml`.
|
||||
|
||||
## Development Commands
|
||||
|
||||
```bash
|
||||
# Code formatting (Laravel Pint)
|
||||
docker compose run --rm app ./vendor/bin/pint
|
||||
|
||||
# Check code style without fixing
|
||||
docker compose run --rm app ./vendor/bin/pint --test
|
||||
|
||||
# List all routes
|
||||
docker compose run --rm app php artisan route:list
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers\Api\V1;
|
||||
|
||||
use App\Http\Controllers\Api\ApiController;
|
||||
@@ -11,11 +13,11 @@ use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
|
||||
class AuthController extends ApiController
|
||||
final class AuthController extends ApiController
|
||||
{
|
||||
public function register(RegisterRequest $request): JsonResponse
|
||||
{
|
||||
$user = User::create([
|
||||
$user = User::query()->create([
|
||||
'name' => $request->name,
|
||||
'email' => $request->email,
|
||||
'password' => Hash::make($request->password),
|
||||
@@ -31,7 +33,7 @@ class AuthController extends ApiController
|
||||
|
||||
public function login(LoginRequest $request): JsonResponse
|
||||
{
|
||||
$user = User::where('email', $request->email)->first();
|
||||
$user = User::query()->where('email', $request->email)->first();
|
||||
|
||||
if (! $user || ! Hash::check($request->password, $user->password)) {
|
||||
return $this->unauthorized('Invalid credentials');
|
||||
@@ -47,7 +49,9 @@ class AuthController extends ApiController
|
||||
|
||||
public function logout(Request $request): JsonResponse
|
||||
{
|
||||
$request->user()->currentAccessToken()->delete();
|
||||
/** @var User $user */
|
||||
$user = $request->user();
|
||||
$user->currentAccessToken()->delete();
|
||||
|
||||
return $this->success(message: 'Logged out successfully');
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
abstract class Controller
|
||||
|
||||
@@ -1,10 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class LoginRequest extends FormRequest
|
||||
/**
|
||||
* @property string $email
|
||||
* @property string $password
|
||||
*/
|
||||
final class LoginRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
@@ -12,7 +19,7 @@ class LoginRequest extends FormRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Requests\Api\V1;
|
||||
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
|
||||
class RegisterRequest extends FormRequest
|
||||
/**
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property string $password
|
||||
*/
|
||||
final class RegisterRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
@@ -12,7 +20,7 @@ class RegisterRequest extends FormRequest
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||
* @return array<string, ValidationRule|array<mixed>|string>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Http\Resources;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Resources\Json\JsonResource;
|
||||
|
||||
/**
|
||||
* @mixin \App\Models\User
|
||||
* @mixin User
|
||||
*/
|
||||
class UserResource extends JsonResource
|
||||
final class UserResource extends JsonResource
|
||||
{
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
|
||||
@@ -1,17 +1,34 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Database\Factories\UserFactory;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Laravel\Sanctum\HasApiTokens;
|
||||
|
||||
class User extends Authenticatable
|
||||
/**
|
||||
* @property int $id
|
||||
* @property string $name
|
||||
* @property string $email
|
||||
* @property Carbon|null $email_verified_at
|
||||
* @property string $password
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*/
|
||||
final class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasApiTokens, HasFactory, Notifiable;
|
||||
use HasApiTokens;
|
||||
|
||||
/** @use HasFactory<UserFactory> */
|
||||
use HasFactory;
|
||||
|
||||
use Notifiable;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Providers;
|
||||
|
||||
use Illuminate\Cache\RateLimiting\Limit;
|
||||
@@ -7,7 +9,7 @@ use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider
|
||||
final class AppServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Register any application services.
|
||||
@@ -28,23 +30,17 @@ class AppServiceProvider extends ServiceProvider
|
||||
/**
|
||||
* Configure the rate limiters for the application.
|
||||
*/
|
||||
protected function configureRateLimiting(): void
|
||||
private function configureRateLimiting(): void
|
||||
{
|
||||
// Default API rate limiter - 60 requests per minute
|
||||
RateLimiter::for('api', function (Request $request) {
|
||||
return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
|
||||
});
|
||||
RateLimiter::for('api', fn (Request $request) => Limit::perMinute(60)->by($request->user()?->id ?: $request->ip()));
|
||||
|
||||
// Auth endpoints - more restrictive (prevent brute force)
|
||||
RateLimiter::for('auth', function (Request $request) {
|
||||
return Limit::perMinute(5)->by($request->ip());
|
||||
});
|
||||
RateLimiter::for('auth', fn (Request $request) => Limit::perMinute(5)->by($request->ip()));
|
||||
|
||||
// Authenticated user requests - higher limit
|
||||
RateLimiter::for('authenticated', function (Request $request) {
|
||||
return $request->user()
|
||||
? Limit::perMinute(120)->by($request->user()->id)
|
||||
: Limit::perMinute(60)->by($request->ip());
|
||||
});
|
||||
RateLimiter::for('authenticated', fn (Request $request) => $request->user()
|
||||
? Limit::perMinute(120)->by($request->user()->id)
|
||||
: Limit::perMinute(60)->by($request->ip()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Traits;
|
||||
|
||||
use Illuminate\Http\JsonResponse;
|
||||
@@ -31,6 +33,9 @@ trait ApiResponse
|
||||
return response()->json(null, Response::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $errors
|
||||
*/
|
||||
protected function error(
|
||||
string $message = 'Error',
|
||||
int $code = Response::HTTP_BAD_REQUEST,
|
||||
@@ -41,7 +46,7 @@ trait ApiResponse
|
||||
'message' => $message,
|
||||
];
|
||||
|
||||
if (! empty($errors)) {
|
||||
if ($errors !== []) {
|
||||
$response['errors'] = $errors;
|
||||
}
|
||||
|
||||
@@ -63,6 +68,9 @@ trait ApiResponse
|
||||
return $this->error($message, Response::HTTP_FORBIDDEN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $errors
|
||||
*/
|
||||
protected function validationError(array $errors, string $message = 'Validation failed'): JsonResponse
|
||||
{
|
||||
return $this->error($message, Response::HTTP_UNPROCESSABLE_ENTITY, $errors);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Foundation\Configuration\Exceptions;
|
||||
use Illuminate\Foundation\Configuration\Middleware;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
App\Providers\AppServiceProvider::class,
|
||||
];
|
||||
|
||||
@@ -16,13 +16,16 @@
|
||||
"spatie/laravel-query-builder": "^6.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"driftingly/rector-laravel": "^2.0",
|
||||
"fakerphp/faker": "^1.23",
|
||||
"larastan/larastan": "^3.7",
|
||||
"laravel/pail": "^1.2.2",
|
||||
"laravel/pint": "^1.24",
|
||||
"mockery/mockery": "^1.6",
|
||||
"nunomaduro/collision": "^8.6",
|
||||
"pestphp/pest": "^4.0",
|
||||
"pestphp/pest-plugin-laravel": "^4.0"
|
||||
"pestphp/pest-plugin-laravel": "^4.0",
|
||||
"rector/rector": "^2.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@@ -37,10 +40,26 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"test": [
|
||||
"lint": [
|
||||
"rector",
|
||||
"pint"
|
||||
],
|
||||
"test:lint": [
|
||||
"pint --test",
|
||||
"rector --dry-run"
|
||||
],
|
||||
"test:types": [
|
||||
"phpstan"
|
||||
],
|
||||
"test:unit": [
|
||||
"@php artisan config:clear --ansi",
|
||||
"@php artisan test"
|
||||
],
|
||||
"test": [
|
||||
"@test:lint",
|
||||
"@test:types",
|
||||
"@test:unit"
|
||||
],
|
||||
"post-autoload-dump": [
|
||||
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
|
||||
"@php artisan package:discover --ansi"
|
||||
|
||||
282
composer.lock
generated
282
composer.lock
generated
@@ -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": "07247fcb148cb2f2dd5ce91bdef3c5e1",
|
||||
"content-hash": "76745da02f7c3b291785aa3cd83bd614",
|
||||
"packages": [
|
||||
{
|
||||
"name": "brick/math",
|
||||
@@ -7090,6 +7090,42 @@
|
||||
],
|
||||
"time": "2026-01-08T07:23:06+00:00"
|
||||
},
|
||||
{
|
||||
"name": "driftingly/rector-laravel",
|
||||
"version": "2.1.9",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/driftingly/rector-laravel.git",
|
||||
"reference": "aee9d4a1d489e7ec484fc79f33137f8ee051b3f7"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/aee9d4a1d489e7ec484fc79f33137f8ee051b3f7",
|
||||
"reference": "aee9d4a1d489e7ec484fc79f33137f8ee051b3f7",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"rector/rector": "^2.2.7",
|
||||
"webmozart/assert": "^1.11"
|
||||
},
|
||||
"type": "rector-extension",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"RectorLaravel\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Rector upgrades rules for Laravel Framework",
|
||||
"support": {
|
||||
"issues": "https://github.com/driftingly/rector-laravel/issues",
|
||||
"source": "https://github.com/driftingly/rector-laravel/tree/2.1.9"
|
||||
},
|
||||
"time": "2025-12-25T23:31:36+00:00"
|
||||
},
|
||||
{
|
||||
"name": "fakerphp/faker",
|
||||
"version": "v1.24.1",
|
||||
@@ -7336,6 +7372,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",
|
||||
@@ -7396,6 +7473,96 @@
|
||||
},
|
||||
"time": "2025-03-19T14:43:43+00:00"
|
||||
},
|
||||
{
|
||||
"name": "larastan/larastan",
|
||||
"version": "v3.9.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/larastan/larastan.git",
|
||||
"reference": "82c18890d0d5b012bc39a3432531e5b6cd1b4b3a"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/larastan/larastan/zipball/82c18890d0d5b012bc39a3432531e5b6cd1b4b3a",
|
||||
"reference": "82c18890d0d5b012bc39a3432531e5b6cd1b4b3a",
|
||||
"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.32"
|
||||
},
|
||||
"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",
|
||||
"phpmyadmin/sql-parser": "Install to enable Larastan's optional phpMyAdmin-based SQL parser automatically"
|
||||
},
|
||||
"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.9.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/canvural",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-17T23:00:37+00:00"
|
||||
},
|
||||
{
|
||||
"name": "laravel/pail",
|
||||
"version": "v1.2.4",
|
||||
@@ -8304,6 +8471,59 @@
|
||||
},
|
||||
"time": "2022-02-21T01:04:05+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpstan/phpstan",
|
||||
"version": "2.1.35",
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/72f843c7f59d3aac0b7510f5e70913a9b72a8e88",
|
||||
"reference": "72f843c7f59d3aac0b7510f5e70913a9b72a8e88",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^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": "2026-01-20T17:33:48+00:00"
|
||||
},
|
||||
{
|
||||
"name": "phpunit/php-code-coverage",
|
||||
"version": "12.5.2",
|
||||
@@ -8743,6 +8963,66 @@
|
||||
],
|
||||
"time": "2025-12-15T06:05:34+00:00"
|
||||
},
|
||||
{
|
||||
"name": "rector/rector",
|
||||
"version": "2.3.2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/rectorphp/rector.git",
|
||||
"reference": "07cbbe28bd60251b96b18d42e514779b0e2faa83"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/rectorphp/rector/zipball/07cbbe28bd60251b96b18d42e514779b0e2faa83",
|
||||
"reference": "07cbbe28bd60251b96b18d42e514779b0e2faa83",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.4|^8.0",
|
||||
"phpstan/phpstan": "^2.1.34"
|
||||
},
|
||||
"conflict": {
|
||||
"rector/rector-doctrine": "*",
|
||||
"rector/rector-downgrade-php": "*",
|
||||
"rector/rector-phpunit": "*",
|
||||
"rector/rector-symfony": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-dom": "To manipulate phpunit.xml via the custom-rule command"
|
||||
},
|
||||
"bin": [
|
||||
"bin/rector"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"bootstrap.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "Instant Upgrade and Automated Refactoring of any PHP code",
|
||||
"homepage": "https://getrector.com/",
|
||||
"keywords": [
|
||||
"automation",
|
||||
"dev",
|
||||
"migration",
|
||||
"refactoring"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/rectorphp/rector/issues",
|
||||
"source": "https://github.com/rectorphp/rector/tree/2.3.2"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://github.com/tomasvotruba",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-20T01:11:51+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
"version": "4.2.0",
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
@@ -62,7 +66,7 @@ return [
|
||||
'providers' => [
|
||||
'users' => [
|
||||
'driver' => 'eloquent',
|
||||
'model' => env('AUTH_MODEL', App\Models\User::class),
|
||||
'model' => env('AUTH_MODEL', User::class),
|
||||
],
|
||||
|
||||
// 'users' => [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
@@ -59,7 +61,7 @@ return [
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
@@ -79,7 +81,7 @@ return [
|
||||
'strict' => true,
|
||||
'engine' => null,
|
||||
'options' => extension_loaded('pdo_mysql') ? array_filter([
|
||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : \PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
(PHP_VERSION_ID >= 80500 ? \Pdo\Mysql::ATTR_SSL_CA : PDO::MYSQL_ATTR_SSL_CA) => env('MYSQL_ATTR_SSL_CA'),
|
||||
]) : [],
|
||||
],
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Monolog\Handler\NullHandler;
|
||||
use Monolog\Handler\StreamHandler;
|
||||
use Monolog\Handler\SyslogUdpHandler;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Cookie\Middleware\EncryptCookies;
|
||||
use Illuminate\Foundation\Http\Middleware\ValidateCsrfToken;
|
||||
use Laravel\Sanctum\Http\Middleware\AuthenticateSession;
|
||||
use Laravel\Sanctum\Sanctum;
|
||||
|
||||
return [
|
||||
@@ -15,7 +20,7 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'stateful' => explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'stateful' => explode(',', (string) env('SANCTUM_STATEFUL_DOMAINS', sprintf(
|
||||
'%s%s',
|
||||
'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1',
|
||||
Sanctum::currentApplicationUrlWithPort(),
|
||||
@@ -76,9 +81,9 @@ return [
|
||||
*/
|
||||
|
||||
'middleware' => [
|
||||
'authenticate_session' => Laravel\Sanctum\Http\Middleware\AuthenticateSession::class,
|
||||
'encrypt_cookies' => Illuminate\Cookie\Middleware\EncryptCookies::class,
|
||||
'validate_csrf_token' => Illuminate\Foundation\Http\Middleware\ValidateCsrfToken::class,
|
||||
'authenticate_session' => AuthenticateSession::class,
|
||||
'encrypt_cookies' => EncryptCookies::class,
|
||||
'validate_csrf_token' => ValidateCsrfToken::class,
|
||||
],
|
||||
|
||||
];
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Dedoc\Scramble\Http\Middleware\RestrictedDocsAccess;
|
||||
|
||||
return [
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
return [
|
||||
|
||||
/*
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
return [
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\User>
|
||||
* @extends Factory<User>
|
||||
*/
|
||||
class UserFactory extends Factory
|
||||
final class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
* The current password being used by the factory.
|
||||
*/
|
||||
protected static ?string $password;
|
||||
private static ?string $password = null;
|
||||
|
||||
/**
|
||||
* Define the model's default state.
|
||||
@@ -27,7 +30,7 @@ class UserFactory extends Factory
|
||||
'name' => fake()->name(),
|
||||
'email' => fake()->unique()->safeEmail(),
|
||||
'email_verified_at' => now(),
|
||||
'password' => static::$password ??= Hash::make('password'),
|
||||
'password' => self::$password ??= Hash::make('password'),
|
||||
'remember_token' => Str::random(10),
|
||||
];
|
||||
}
|
||||
@@ -37,7 +40,7 @@ class UserFactory extends Factory
|
||||
*/
|
||||
public function unverified(): static
|
||||
{
|
||||
return $this->state(fn (array $attributes) => [
|
||||
return $this->state(fn (array $attributes): array => [
|
||||
'email_verified_at' => null,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -11,7 +13,7 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('users', function (Blueprint $table) {
|
||||
Schema::create('users', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('name');
|
||||
$table->string('email')->unique();
|
||||
@@ -21,13 +23,13 @@ return new class extends Migration
|
||||
$table->timestamps();
|
||||
});
|
||||
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table) {
|
||||
Schema::create('password_reset_tokens', function (Blueprint $table): void {
|
||||
$table->string('email')->primary();
|
||||
$table->string('token');
|
||||
$table->timestamp('created_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('sessions', function (Blueprint $table) {
|
||||
Schema::create('sessions', function (Blueprint $table): void {
|
||||
$table->string('id')->primary();
|
||||
$table->foreignId('user_id')->nullable()->index();
|
||||
$table->string('ip_address', 45)->nullable();
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -11,13 +13,13 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('cache', function (Blueprint $table) {
|
||||
Schema::create('cache', function (Blueprint $table): void {
|
||||
$table->string('key')->primary();
|
||||
$table->mediumText('value');
|
||||
$table->integer('expiration');
|
||||
});
|
||||
|
||||
Schema::create('cache_locks', function (Blueprint $table) {
|
||||
Schema::create('cache_locks', function (Blueprint $table): void {
|
||||
$table->string('key')->primary();
|
||||
$table->string('owner');
|
||||
$table->integer('expiration');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -11,7 +13,7 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('jobs', function (Blueprint $table) {
|
||||
Schema::create('jobs', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('queue')->index();
|
||||
$table->longText('payload');
|
||||
@@ -21,7 +23,7 @@ return new class extends Migration
|
||||
$table->unsignedInteger('created_at');
|
||||
});
|
||||
|
||||
Schema::create('job_batches', function (Blueprint $table) {
|
||||
Schema::create('job_batches', function (Blueprint $table): void {
|
||||
$table->string('id')->primary();
|
||||
$table->string('name');
|
||||
$table->integer('total_jobs');
|
||||
@@ -34,7 +36,7 @@ return new class extends Migration
|
||||
$table->integer('finished_at')->nullable();
|
||||
});
|
||||
|
||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||
Schema::create('failed_jobs', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->text('connection');
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
@@ -11,7 +13,7 @@ return new class extends Migration
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table) {
|
||||
Schema::create('personal_access_tokens', function (Blueprint $table): void {
|
||||
$table->id();
|
||||
$table->morphs('tokenable');
|
||||
$table->text('name');
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class DatabaseSeeder extends Seeder
|
||||
final class DatabaseSeeder extends Seeder
|
||||
{
|
||||
use WithoutModelEvents;
|
||||
|
||||
|
||||
21
phpstan.neon
Normal file
21
phpstan.neon
Normal file
@@ -0,0 +1,21 @@
|
||||
includes:
|
||||
- vendor/larastan/larastan/extension.neon
|
||||
- vendor/nesbot/carbon/extension.neon
|
||||
- phar://phpstan.phar/conf/bleedingEdge.neon
|
||||
|
||||
parameters:
|
||||
|
||||
paths:
|
||||
- app
|
||||
- bootstrap/app.php
|
||||
- config
|
||||
- database
|
||||
- public
|
||||
- routes
|
||||
|
||||
excludePaths:
|
||||
- config/database.php
|
||||
|
||||
level: max
|
||||
|
||||
tmpDir: /tmp/phpstan
|
||||
63
pint.json
Normal file
63
pint.json
Normal file
@@ -0,0 +1,63 @@
|
||||
{
|
||||
"preset": "laravel",
|
||||
"notPath": [
|
||||
"tests/TestCase.php",
|
||||
"tmp",
|
||||
"config/database.php"
|
||||
],
|
||||
"rules": {
|
||||
"array_push": true,
|
||||
"backtick_to_shell_exec": true,
|
||||
"date_time_immutable": true,
|
||||
"declare_strict_types": true,
|
||||
"lowercase_keywords": true,
|
||||
"lowercase_static_reference": true,
|
||||
"final_class": true,
|
||||
"final_internal_class": true,
|
||||
"final_public_method_for_abstract_class": true,
|
||||
"fully_qualified_strict_types": true,
|
||||
"global_namespace_import": {
|
||||
"import_classes": true,
|
||||
"import_constants": true,
|
||||
"import_functions": true
|
||||
},
|
||||
"mb_str_functions": true,
|
||||
"modernize_types_casting": true,
|
||||
"new_with_parentheses": false,
|
||||
"no_superfluous_elseif": true,
|
||||
"no_useless_else": true,
|
||||
"no_multiple_statements_per_line": true,
|
||||
"ordered_class_elements": {
|
||||
"order": [
|
||||
"use_trait",
|
||||
"case",
|
||||
"constant",
|
||||
"constant_public",
|
||||
"constant_protected",
|
||||
"constant_private",
|
||||
"property_public",
|
||||
"property_protected",
|
||||
"property_private",
|
||||
"construct",
|
||||
"destruct",
|
||||
"magic",
|
||||
"phpunit",
|
||||
"method_abstract",
|
||||
"method_public_static",
|
||||
"method_public",
|
||||
"method_protected_static",
|
||||
"method_protected",
|
||||
"method_private_static",
|
||||
"method_private"
|
||||
],
|
||||
"sort_algorithm": "none"
|
||||
},
|
||||
"ordered_interfaces": true,
|
||||
"ordered_traits": true,
|
||||
"protected_to_private": true,
|
||||
"self_accessor": true,
|
||||
"self_static_accessor": true,
|
||||
"strict_comparison": true,
|
||||
"visibility_required": true
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
|
||||
54
rector.php
Normal file
54
rector.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Caching\ValueObject\Storage\FileCacheStorage;
|
||||
use Rector\Config\RectorConfig;
|
||||
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;
|
||||
use RectorLaravel\Set\LaravelSetList;
|
||||
use RectorLaravel\Set\LaravelSetProvider;
|
||||
|
||||
return RectorConfig::configure()
|
||||
->withSetProviders(LaravelSetProvider::class)
|
||||
->withSets([
|
||||
LaravelSetList::LARAVEL_ARRAYACCESS_TO_METHOD_CALL,
|
||||
LaravelSetList::LARAVEL_ARRAY_STR_FUNCTION_TO_STATIC_CALL,
|
||||
LaravelSetList::LARAVEL_CODE_QUALITY,
|
||||
LaravelSetList::LARAVEL_COLLECTION,
|
||||
LaravelSetList::LARAVEL_CONTAINER_STRING_TO_FULLY_QUALIFIED_NAME,
|
||||
LaravelSetList::LARAVEL_ELOQUENT_MAGIC_METHOD_TO_QUERY_BUILDER,
|
||||
LaravelSetList::LARAVEL_FACADE_ALIASES_TO_FULL_NAMES,
|
||||
LaravelSetList::LARAVEL_FACTORIES,
|
||||
LaravelSetList::LARAVEL_IF_HELPERS,
|
||||
LaravelSetList::LARAVEL_LEGACY_FACTORIES_TO_CLASSES,
|
||||
])
|
||||
->withImportNames(
|
||||
removeUnusedImports: true,
|
||||
)
|
||||
->withComposerBased(laravel: true)
|
||||
->withCache(
|
||||
cacheDirectory: '/tmp/rector',
|
||||
cacheClass: FileCacheStorage::class,
|
||||
)
|
||||
->withPaths([
|
||||
__DIR__.'/app',
|
||||
__DIR__.'/bootstrap/app.php',
|
||||
__DIR__.'/config',
|
||||
__DIR__.'/database',
|
||||
__DIR__.'/public',
|
||||
__DIR__.'/routes',
|
||||
__DIR__.'/tests',
|
||||
])
|
||||
->withSkip([
|
||||
AddOverrideAttributeToOverriddenMethodsRector::class,
|
||||
__DIR__.'/config/database.php',
|
||||
])
|
||||
->withPreparedSets(
|
||||
deadCode: true,
|
||||
codeQuality: true,
|
||||
typeDeclarations: true,
|
||||
privatization: true,
|
||||
earlyReturn: true,
|
||||
codingStyle: true,
|
||||
)
|
||||
->withPhpSets();
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| API Routes
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Http\Controllers\Api\V1\AuthController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
@@ -13,13 +15,13 @@ use Illuminate\Support\Facades\Route;
|
||||
*/
|
||||
|
||||
// Public routes with auth rate limiter (5/min - brute force protection)
|
||||
Route::middleware('throttle:auth')->group(function () {
|
||||
Route::middleware('throttle:auth')->group(function (): void {
|
||||
Route::post('register', [AuthController::class, 'register'])->name('api.v1.register');
|
||||
Route::post('login', [AuthController::class, 'login'])->name('api.v1.login');
|
||||
});
|
||||
|
||||
// Protected routes with authenticated rate limiter (120/min)
|
||||
Route::middleware(['auth:sanctum', 'throttle:authenticated'])->group(function () {
|
||||
Route::middleware(['auth:sanctum', 'throttle:authenticated'])->group(function (): void {
|
||||
Route::post('logout', [AuthController::class, 'logout'])->name('api.v1.logout');
|
||||
Route::get('me', [AuthController::class, 'me'])->name('api.v1.me');
|
||||
});
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
Artisan::command('inspire', function (): void {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
// Web routes disabled - API only application
|
||||
// Scramble documentation available at /docs/api
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
||||
uses(RefreshDatabase::class);
|
||||
|
||||
describe('Registration', function () {
|
||||
it('registers a new user successfully', function () {
|
||||
describe('Registration', function (): void {
|
||||
it('registers a new user successfully', function (): void {
|
||||
$response = $this->postJson('/api/v1/register', [
|
||||
'name' => 'Test User',
|
||||
'email' => 'test@example.com',
|
||||
@@ -33,7 +35,7 @@ describe('Registration', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails registration with invalid data', function () {
|
||||
it('fails registration with invalid data', function (): void {
|
||||
$response = $this->postJson('/api/v1/register', [
|
||||
'name' => '',
|
||||
'email' => 'invalid-email',
|
||||
@@ -43,7 +45,7 @@ describe('Registration', function () {
|
||||
$response->assertStatus(422);
|
||||
});
|
||||
|
||||
it('fails registration with duplicate email', function () {
|
||||
it('fails registration with duplicate email', function (): void {
|
||||
User::factory()->create(['email' => 'existing@example.com']);
|
||||
|
||||
$response = $this->postJson('/api/v1/register', [
|
||||
@@ -57,8 +59,8 @@ describe('Registration', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Login', function () {
|
||||
it('logs in with valid credentials', function () {
|
||||
describe('Login', function (): void {
|
||||
it('logs in with valid credentials', function (): void {
|
||||
$user = User::factory()->create([
|
||||
'password' => bcrypt('password123'),
|
||||
]);
|
||||
@@ -83,7 +85,7 @@ describe('Login', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails login with invalid credentials', function () {
|
||||
it('fails login with invalid credentials', function (): void {
|
||||
$user = User::factory()->create([
|
||||
'password' => bcrypt('password123'),
|
||||
]);
|
||||
@@ -100,7 +102,7 @@ describe('Login', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails login with non-existent user', function () {
|
||||
it('fails login with non-existent user', function (): void {
|
||||
$response = $this->postJson('/api/v1/login', [
|
||||
'email' => 'nonexistent@example.com',
|
||||
'password' => 'password123',
|
||||
@@ -110,12 +112,12 @@ describe('Login', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Logout', function () {
|
||||
it('logs out authenticated user', function () {
|
||||
describe('Logout', function (): void {
|
||||
it('logs out authenticated user', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$token = $user->createToken('test-token')->plainTextToken;
|
||||
|
||||
$response = $this->withHeader('Authorization', "Bearer {$token}")
|
||||
$response = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->postJson('/api/v1/logout');
|
||||
|
||||
$response->assertStatus(200)
|
||||
@@ -125,19 +127,19 @@ describe('Logout', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails logout without authentication', function () {
|
||||
it('fails logout without authentication', function (): void {
|
||||
$response = $this->postJson('/api/v1/logout');
|
||||
|
||||
$response->assertStatus(401);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Me', function () {
|
||||
it('returns authenticated user data', function () {
|
||||
describe('Me', function (): void {
|
||||
it('returns authenticated user data', function (): void {
|
||||
$user = User::factory()->create();
|
||||
$token = $user->createToken('test-token')->plainTextToken;
|
||||
|
||||
$response = $this->withHeader('Authorization', "Bearer {$token}")
|
||||
$response = $this->withHeader('Authorization', 'Bearer '.$token)
|
||||
->getJson('/api/v1/me');
|
||||
|
||||
$response->assertStatus(200)
|
||||
@@ -155,7 +157,7 @@ describe('Me', function () {
|
||||
]);
|
||||
});
|
||||
|
||||
it('fails without authentication', function () {
|
||||
it('fails without authentication', function (): void {
|
||||
$response = $this->getJson('/api/v1/me');
|
||||
|
||||
$response->assertStatus(401);
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Tests\TestCase;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Test Case
|
||||
@@ -11,7 +15,7 @@
|
||||
|
|
||||
*/
|
||||
|
||||
pest()->extend(Tests\TestCase::class)
|
||||
pest()->extend(TestCase::class)
|
||||
// ->use(Illuminate\Foundation\Testing\RefreshDatabase::class)
|
||||
->in('Feature');
|
||||
|
||||
@@ -26,9 +30,7 @@ pest()->extend(Tests\TestCase::class)
|
||||
|
|
||||
*/
|
||||
|
||||
expect()->extend('toBeOne', function () {
|
||||
return $this->toBe(1);
|
||||
});
|
||||
expect()->extend('toBeOne', fn () => $this->toBe(1));
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -41,7 +43,7 @@ expect()->extend('toBeOne', function () {
|
||||
|
|
||||
*/
|
||||
|
||||
function something()
|
||||
function something(): void
|
||||
{
|
||||
// ..
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
final class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
|
||||
Reference in New Issue
Block a user