I have one question: Why is every class final, like every single class in your whole project?
I’m extremely confused about my code structure and I’m hoping a senior developer can help me out
My confusion: I’m using PHPStan at the maximum level, which means I have to write a lot of heavy annotations just to satisfy PHPStan. Also, I’ve never used DTOs before — this is my first project using them — so I’m not even sure if I’m following the correct approach.
What’s troubling me the most is that after validating the data, when I pass it into the DTO, PHPStan keeps giving warnings because of mixed values.
I’m sharing my project’s GitHub link below. If anyone kind-hearted could take a look at my code, point out where I’m going wrong, and guide me toward the proper way to structure things, I would really appreciate it.
github.com/shahriarshaon1993/myfinance
My DTO: RoleDto.php
final readonly class AccountTypeDto
{
public function __construct(
public string $code,
public string $name,
public bool $normalBalanceDebit,
public string $isActive,
public ?string $description = null,
) {
//
}
/**
* @param array{code: string, name: string, normal_balance_debit: bool, is_active: string, description: string|null} $data
*/
public static function fromArray(array $data): self
{
return new self(
code: $data['code'],
name: $data['name'],
normalBalanceDebit: (bool) $data['normal_balance_debit'],
isActive: $data['is_active'],
description: $data['description'] ?? null,
);
}
/**
* @return array{code: string, name: string, normal_balance_debit: bool, is_active: string, description: string|null}
*/
public function toArray(): array
{
return [
'code' => $this->code,
'name' => $this->name,
'normal_balance_debit' => $this->normalBalanceDebit,
'is_active' => $this->isActive,
'description' => $this->description,
];
}
}
Request validation: StoreAccountTypeRequest.php
public function rules(): array
{
return [
'code' => ['required', 'string', 'min:2', 'max:50', 'regex:/^[A-Z_]+$/', 'unique:account_types,code'],
'name' => ['required', 'string', 'min:2', 'max:50'],
'description' => ['nullable', 'string', 'min:2', 'max:500'],
'normal_balance_debit' => ['required', 'boolean'],
'is_active' => [Rule::enum(ActiveStatus::class)],
];
}
/**
* @return string[]
*/
public function messages(): array
{
return [
'code.regex' => 'The code may only contain uppercase letters and underscores.',
];
}
Controller is: AccountTypeController.php
public function store(StoreAccountTypeRequest $request, CreateType $action): RedirectResponse
{
Gate::authorize('create', AccountType::class);
/** @var array{code: string, name: string, normal_balance_debit: bool, is_active: string, description: string|null} $data */
$data = $request->validated();
$action->handle(AccountTypeDto::fromArray($data));
return to_route('accounting.types.index')
->with('success', 'Account type created successfully.');
}
Store action class: CreateAccountType.php
public function handle(AccountTypeDto $type): AccountType
{
return AccountType::create($type->toArray());
}
Please or to participate in this conversation.