Esplorando la gestione delle tipizzazioni in PHP
Per gestire i parametri di tipo array in PHP, possiamo usare semplicemente il tipo array, oppure esplorare soluzioni come DTOs o la classe Collection di Laravel. Un’altra opzione è utilizzare oggetti di valore personalizzati, generics tramite phpdoc o proprietà tipizzate accompagnate da docblock per migliori suggerimenti IDE. Laravel non supporta la tipizzazione strutturale sugli array, quindi per fare validazioni più precise, possiamo usare metodi come Arr::get.
1. Tipizzare un «menu item» in un componente Blade
PHP oggi non ha tipi “shape” nativi (ogni chiave dell’array con un proprio tipo), ma possiamo:
-
Accettare un array tipizzato via PHPDoc – perfetto se vuoi massima semplicità e compatibilità.
-
Creare un piccolo DTO (Data‑Transfer‑Object) – ottimo se desideri un vero tipo, validazione centralizzata e autocomplete IDE.
Di seguito trovi entrambi gli approcci: scegli tu quale adottare.
A) Soluzione rapida con array + PHPDoc shaped array
namespace App\View\Layout\Main;
use App\View\Puk\PukComponent;
/**
* @property array{
* label: string,
* url: string,
* icon?: string,
* children?: array<string, array> // ricorsivo
* } $item
*/
class MenuItem extends PukComponent
{
/** @var array<string, mixed> */
public array $item;
/**
* @param array{
* label: string,
* url: string,
* icon?: string,
* children?: array<string, array>
* } $item
*/
public function __construct(array $item)
{
parent::__construct(); // richiama la logica di PukComponent
$this->item = $item; // l’IDE saprà le chiavi disponibili
}
public function render()
{
return view('layout.main.menu-item');
}
}Blade (resources/views/layout/main/menu-item.blade.php)
<li x-data="{ open: false }" class="relative">
{{-- link principale --}}
<a href="{{ $item['url'] }}"
class="flex items-center gap-2 px-3 py-2 rounded-md hover:bg-indigo-600/20"
@if(isset($item['children'])) @click.prevent="open = !open" @endif>
{{-- icona dinamica (blade-icons) --}}
<x-dynamic-component :component="$item['icon'] ?? 'heroicon-s-link'"
class="shrink-0 size-5 text-indigo-400"/>
<span class="flex-1">{{ $item['label'] }}</span>
{{-- caret per i figli --}}
@isset($item['children'])
<x-heroicon-s-chevron-down x-show="!open" class="size-4 text-indigo-300"/>
<x-heroicon-s-chevron-up x-show="open" class="size-4 text-indigo-300"/>
@endisset
</a>
{{-- sotto‑voci, ricorsivo --}}
@isset($item['children'])
<ul x-show="open" x-collapse class="mt-1 space-y-1 pl-4 border-l border-indigo-700/30">
@foreach($item['children'] as $child)
<x-layout::main.menu-item :item="$child"/>
@endforeach
</ul>
@endisset
</li>Pro: minimo sforzo.
Contro: nessuna garanzia forte a run‑time; se mancano chiavi ti accorgi solo all’esecuzione.
B) Soluzione “robusta” con DTO (MenuItemData)
DTO: app/Data/MenuItemData.php
namespace App\Data;
use Illuminate\Support\Collection;
final readonly class MenuItemData
{
/** @param Collection<int, self>|self[] $children */
public function __construct(
public string $label,
public string $url,
public ?string $icon = null,
public array|Collection $children = [],
) {}
/** Costruzione “ricorsiva” da array normale */
public static function from(array $data): self
{
$children = collect($data['children'] ?? [])
->map(fn (array $child) => self::from($child));
return new self(
label: $data['label'],
url: $data['url'],
icon: $data['icon'] ?? null,
children: $children,
);
}
}Componente: MenuItem.php
use App\Data\MenuItemData;
use App\View\Puk\PukComponent;
class MenuItem extends PukComponent
{
public MenuItemData $item;
public function __construct(MenuItemData|array $item)
{
parent::__construct();
// consenti sia array che DTO
$this->item = $item instanceof MenuItemData
? $item
: MenuItemData::from($item);
}
public function render()
{
return view('layout.main.menu-item');
}
}Blade per il DTO
@php($hasChildren = $item->children->isNotEmpty())
<li x-data="{ open: false }" class="relative">
<a href="{{ $item->url }}"
class="flex items-center gap-2 px-3 py-2 rounded-md hover:bg-indigo-600/20"
@if($hasChildren) @click.prevent="open = !open" @endif>
<x-dynamic-component :component="$item->icon ?? 'heroicon-s-link'"
class="shrink-0 size-5 text-indigo-400"/>
<span class="flex-1">{{ $item->label }}</span>
@if($hasChildren)
<x-heroicon-s-chevron-down x-show="!open" class="size-4 text-indigo-300"/>
<x-heroicon-s-chevron-up x-show="open" class="size-4 text-indigo-300"/>
@endif
</a>
@if($hasChildren)
<ul x-show="open" x-collapse class="mt-1 space-y-1 pl-4 border-l border-indigo-700/30">
@foreach($item->children as $child)
<x-layout::main.menu-item :item="$child"/>
@endforeach
</ul>
@endif
</li>Pro: tipo forte, validazione in un solo punto, autocomplete preciso.
Contro: qualche riga di codice in più.
{{-- $exampleMenu è l’array/DTO visto in precedenza --}}
@foreach($exampleMenu as $menuItem)
<x-layout::main.menu-item :item="$menuItem"/>
@endforeachEntrambe le versioni (array tipizzato o DTO) si integrano perfettamente con Alpine JS e Tailwind; scegli quella che si adatta meglio al tuo flusso di lavoro. In caso di dubbi o se vuoi aggiungere altre proprietà (badge, permessi, ecc.) fammi sapere!