Relations & Nesting: Start by defining a relations() method in your AdminResource to declare one-to-many or many-to-many relationships. For example:
public static function relations(): array
{
return [
Relation::make('comments', CommentResource::class)
->oneToMany()
->foreignKey('article_id'),
];
}
->using(RelationManagerConfiguration::class) to extract relation logic into a dedicated class for complex setups.Nested Resources: Declare a parent relationship in your resource:
public static function parent(): ?ParentRelation
{
return ParentRelation::make(ProjectResource::class)
->relationship('tasks')
->foreignKey('project_id')
->recordTitle('name');
}
/{prefix}/{parentResource}/{parentId}/{resource}/...) are auto-generated.Record View Pages: Add a read-only view page to your resource:
public static function view(Schema $schema): Schema
{
return $schema->columns([
Entry::make('title', 'Title'),
Entry::make('content', 'Content'),
]);
}
Custom Identifier Field: Override getIdentifierField() for non-standard keys (e.g., slugs):
public static function getIdentifierField(): string
{
return 'slug';
}
Article → Comments). Use the auto-generated inline modal for CRUD operations.Project → Tasks) and leverage parent-scoped filtering/URLs.Relation Managers:
Relation::readOnlyOnView(true) to disable editing on the view page.RelationManagerConfiguration class for reusability.Nested Resources:
parentResourceSlug in PageContext for nested URLs.Projects › Alpha › Tasks). Override ParentRelation::recordTitle() for custom display names.View Pages:
Entry::make('title')) in a Schema. Falls back to form() fields if view() is omitted.Custom Identifiers:
getIdentifierField() to use fields like slug or uuid. Ensure scopeQuery() handles custom lookups.canAssociate(), canDetach(), or canAttach() to restrict relation actions.RelationDataProvider for custom pivot-table logic (e.g., Doctrine or array adapters).RelationManagers or embed Atrium\Action\Form with relationResource/relationName for modal forms.path_prefix with parent() for nested routes. Use nestedUrl() in PageContext for dynamic URLs.Breaking Changes:
Relation::form() Activation: Pre-0.2.0, inline form() closures were ignored. Now, they’re applied in modals. Update resources using Relation::form(...).DataProviderInterface::find(): Signature changed to include $idField. Third-party providers must implement the new parameter.ActionContext: No longer final; extend NestedActionContext for nested routes.Nested Resources:
parent() against the registry. Ensure foreignKey() matches the parent’s relation.scopeQuery()./{prefix}/{parentResource}/{parentId}/{resource}/...). Avoid overlapping with flat routes.Relation Managers:
pivotColumns() for data but not UI yet.can(...) permissions, not the parent’s.Relation::visible(fn ($parent) => ...) to hide managers conditionally.Custom Identifiers:
WHERE <field> = :id for custom fields. Avoid complex queries in scopeQuery().getIdentifierField() returns a field that’s both readable and resolvable (e.g., slug must exist in the DB).Relation Errors:
relations() for valid oneToMany()/manyToMany() declarations and matching foreign keys.php artisan atrium:debug:relations (if available) to inspect registered relations.Nested Routes:
parent() returns a valid ParentRelation with a registered parent resource.route('atrium.admin.resources.{resource}.index') and pass parentId.View Pages:
view() is empty, the system falls back to form() fields. Explicitly return Schema::make() to avoid surprises.dd($this->record) in ViewPage to inspect the resolved record.Authorization:
canAssociate() in your AdminResource. Log denials to debug:
public static function canAssociate($parent, $child): bool
{
if (!someCondition($parent, $child)) {
\Log::debug('Association denied', ['parent' => $parent, 'child' => $child]);
return false;
}
return true;
}
Custom Relation Managers:
RelationManagerConfiguration to override table columns, forms, or actions.public function table(): Table
{
return parent::table()
->action('archive', 'Archive')
->url(fn ($record) => route('atrium.admin.relations.archive', [
'resource' => $this->resource,
'relation' => $this->relationName,
'id' => $record->id,
]));
}
Data Providers:
RelationDataProvider for non-Doctrine setups (e.g., Eloquent with custom queries):
class CustomRelationDataProvider implements RelationDataProvider
{
public function findManyByForeignKey(string $foreignKey, array $values): array
{
return YourModel::where($foreignKey, $values)->get();
}
}
Nested Actions:
NestedActionContext to add custom logic to nested routes (e.g., middleware):
class CustomNestedContext extends NestedActionContext
{
public function getMiddleware(): array
{
return array_merge(parent::getMiddleware(), [
\App\Http\Middleware\CheckProjectAccess::class,
]);
}
}
View Entries:
Entry classes (e.g., MarkdownEntry) by extending Atrium\View\Entry:
class MarkdownEntry extends Entry
{
public function render(): string
{
return marked($this->value);
}
}
Use in view():
Entry::make('description', 'Description')->type(MarkdownEntry::class)
Icon Migration:
panel icons with Symfony UX Icons. Update your resource classes:
public static function icon(): string
{
return 'heroicon-o-collection'; // Symfony UX Icon name
}
Check the Symfony UX Icons documentation for available icons.How can I help you explore Laravel packages today?