Tablas y columnas
Las columnas definen qué se muestra en el listado de un Resource. Se declaran dentro de columns():
public function columns(): array
{
return [
Column::make('title')->sortable()->searchable(),
Column::make('is_published')->as('boolean')->sortable(),
Column::make('published_at')->as('date')->displayFormat('d/m/Y')->sortable(),
Column::make('status')
->as('badge')
->value(fn (Post $post): string => $post->is_published ? 'Published' : 'Draft')
->color(fn (Post $post): string => $post->is_published ? 'success' : 'gray'),
];
}API de Column
| Método | Descripción |
|---|---|
make(string $name) | El nombre del atributo en el modelo Eloquent (o el nombre lógico, si usas value()). |
label(string $label) | Etiqueta de la cabecera. Por defecto, Str::headline($name). |
as(string $type) | Cómo se renderiza la celda en el frontend: text (por defecto), boolean, date, badge… |
sortable(bool $sortable = true) | Permite ordenar el listado por esta columna. |
hiddenByDefault(bool $hidden = true) | Oculta por defecto; el usuario puede mostrarla desde el selector de columnas y elegir el orden desde el propio listado. |
displayFormat(string $format) | Formato de visualización cuando as('date') (sintaxis date() de PHP). |
value(Closure $callback) | Sustituye el valor de la celda por un valor calculado. Recibe el modelo completo, no solo el atributo. |
color(Closure $callback) | Color de la celda (relevante sobre todo para as('badge')). Recibe el modelo completo. |
circular(bool $circular = true) | Renderiza el valor de una columna as('image') como un avatar circular. No hace nada en el resto de tipos. |
url(Closure $callback) | Resuelve un enlace por fila. Recibe el modelo y devuelve la URL, o null para dejar esa fila sin enlace. |
searchable(bool $searchable = true, ?Closure $using = null, ?Closure $refine = null) | Incluye la columna en la búsqueda de texto libre del listado. Con using, sustituye el LIKE por defecto, y con refine, afina el resultado en PHP (véase «Búsqueda a medida»). |
Una columna as('boolean') se renderiza como un icono (✓/✗), no como el texto «Yes»/«No» codificado en inglés.
Columnas con imagen y enlace
Column::make('cover_path')
->as('image')
->circular()
->url(fn (Post $post): ?string => $post->cover_path === null ? null : "/storage/{$post->cover_path}")
->hiddenByDefault()El callback de url() recibe el registro Eloquent y devuelve el enlace. El resultado se añade a cada fila como {columna}_url, de la misma manera que el color resuelto de una columna badge se añade como {columna}_color, no se sirve como propiedad del schema, se calcula por fila.
Búsqueda a medida
Por defecto, searchable() compara el término de búsqueda del índice contra la columna con un LIKE sencillo. Pásale using para columnas que no se pueden comparar así, una columna virtual, o una cifrada:
Column::make('author')
->value(fn (Post $post): ?string => $post->author?->name)
->searchable(using: fn (Builder $query, string $term): Builder => $query->whereHas(
'author',
fn (Builder $author) => $author->where('name', 'like', "%{$term}%")
))La closure recibe un query builder anidado nuevo y el término de búsqueda en crudo; debe mutar el builder que recibe en lugar de devolver otro distinto, porque el where(Closure) de Laravel descarta lo que le devuelvas.
Búsquedas que SQL solo puede estrechar
Una columna cifrada, por ejemplo, no se puede comparar con LIKE, pero sí se puede indexar por un prefijo. Con refine, using solo estrecha los candidatos y refine decide en PHP cuáles coinciden:
Column::make('name')->searchable(
using: fn (Builder $query, string $term): Builder => $query->where('name_index', 'like', mb_substr($term, 0, 3).'%'),
refine: fn (Student $student, string $term): bool => str_contains(mb_strtolower($student->name), mb_strtolower($term)),
)using se ejecuta en SQL y da como máximo 500 candidatos. refine recibe cada candidato y el término, y devuelve si coincide. Los candidatos que pasan se combinan con el resto de columnas buscables del listado.
Columnas calculadas
value() y color() reciben el registro Eloquent entero, no un simple $record->$name: una columna «lógica» que no corresponde a ningún atributo es tan válida como una directa. El ejemplo de status de arriba no lee ninguna columna status de la base de datos, la calcula a partir de is_published.
Column::make('status')
->as('badge')
->value(fn (Post $post): string => $post->is_published ? 'Published' : 'Draft')
->color(fn (Post $post): string => $post->is_published ? 'success' : 'gray')Vista de solo lectura
Cuando un Resource tiene una ViewAction, Arrel reutiliza columns() para renderizar la vista de detalle del registro, con el mismo as(), value() y color() de cada columna. Para agruparlas en secciones con título, declara infolist(). Una entrada vacía se muestra como un guion.