Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Atrium Laravel Package

atriumphp/atrium

View on GitHub
Deep Wiki
Context7
v0.2.0

This release adds the relations & nesting feature (relation managers + nested resources, REL-01..22) and record View pages (VIEW-*), a configurable record identifier field, the migration of panel icons to Symfony UX Icons, and makes path_prefix authoritative for route matching. Pre-1.0 — the public API may still change in a 0.x minor; API-affecting items are flagged below.

Added

  • Relation managers & nested resources — feature complete (REL-01..22). The relations & nesting feature is done across its five milestones: the shared table core + descriptor + read-only one-to-many manager; one-to-many owned create/edit/delete + associate/dissociate; many-to-many attach/detach + tabs; nested resources; and this close-out (extraction, playground, browser-verify, review). Dogfooded in the playground: Article → Comments (1:M, extracted via ->using()), Article ↔ Tags (M:N), Course → Lessons (nested).
  • Extractable relation config — ->using() (REL-19). A relation configures its table()/form() inline by default, or extracts them to a dedicated RelationManagerConfiguration subclass referenced by Relation::using(MyConfig::class) — the same inline-or-dedicated split pages() offers. The manager uses the class's table() for its columns and layers its form() over the target resource's form in the owned create/edit modal. (Behaviour change) wiring this also activates a relation's inline ->form() closure in the modal — previously stored but ignored; a resource that set Relation::form(...) will now see it applied. Atrium:Form gains optional relationResource/relationName mount arguments (additive; defaults preserve behaviour). See Relations › Extracting a relation.
  • Nested resources (REL-14..18, REL-20). A resource becomes nested under a parent record by declaring parent(): ?ParentRelationParentRelation::make(ParentResource::class)->relationship('tasks')->foreignKey('projectId')->recordTitle('name'), validated lazily against the registry (parent registered, the named one-to-many relation exists, foreign keys agree). An explicit nested route family (/{prefix}/{parentResource}/{parentId}/{resource}/… for index/new/{id}/{id}/edit, proven non-colliding with the flat routes) serves full CRUD scoped to the parent: the list filters by the parent foreign key, create presets it, and a child of another parent (a forged id) is a 404 (resolved against both the child's scopeQuery() and the parent FK). A breadcrumb renders the ancestry (Projects › Alpha › Tasks), and a parent-page relation manager whose target is nested links each row into the child's nested view/edit pages and points New at nested create instead of inline modals. (API) new AdminResource::parent() hook (default null = top-level) and the ParentRelation builder; Atrium\Action\ActionContext is no longer finalNestedActionContext extends it to emit 5-segment URLs (additive, not a break); Atrium\Page\PageContext gains optional parentResourceSlug / parentRecordId / parentRecords constructor arguments and a nestedUrl() helper (additive); Atrium:DataTable gains an optional parentId mount argument (additive) that scopes the list and emits nested URLs. See Nesting resources.
  • Relation managers — many-to-many + tabs (REL-02, REL-06, REL-07, REL-08, REL-12). A manyToMany() relation (declared with pivotTable() + pivotKeys() + optional pivotColumns()) renders a manager that Attaches an existing record (a listLinkable picker that excludes already-linked rows, plus a field per pivot column) and Detaches it (row + bulk; the pivot row is removed, both records persist). Pivot reads/writes go through the RelationDataProvider seam (Doctrine via DBAL — the pivot is never mapped as an entity; array adapter via an in-memory pivot store). When a resource declares several relations, the host renders a server-driven tab strip and mounts only the active relation's manager. (API) new default-allow hooks canAttach(object $parent, object $child) / canDetach(...) (override to restrict). Pivot-column display in the related table is a later enhancement. Owned create/edit/delete remain one-to-many only.
  • Relation managers — one-to-many actions (REL-05, REL-06, REL-07, REL-09, REL-12). A one-to-many RelationManager now offers a full lifecycle scoped to the parent: owned Create/Edit (an inline modal hosting the target resource's form — create sets the foreign key in one transaction), Delete + bulk delete, and Associate/Dissociate (link/unlink an existing record via a listLinkable-backed picker). Managers are read-only on the View screen (Relation::readOnlyOnView(), default true) and can be hidden per parent with Relation::visible(fn ($parent) => …). (API) new default-allow authorization hooks AdminResource::canAssociate(object $parent, object $child): bool and canDissociate(...) (one-to-many) and canAttach(...) / canDetach(...) (many-to-many) (override to restrict link/unlink); owned create/edit/delete continue to gate on the target resource's can(...). Atrium:Form gains optional embedded / presetValues / notifyEvent mount arguments (additive; defaults preserve behaviour) so it can be hosted inside a relation manager's modal. Owned row View navigation lands with nested resources (REL-M4).
  • Relations foundation (REL-01..04, REL-10, REL-11, REL-20). A resource declares managed relationships with relations() returning Relation::make(...) descriptors (one-to-many / many-to-many, explicit keys — no Doctrine in core). New storage-agnostic RelationDataProvider seam (Doctrine + array adapters; one-to-many read side) and an AbstractRecordTable core extracted from DataTable (no behaviour change). A read-only RelationManager Live Component renders a parent-scoped related table, embedded on the Edit page via a RelationManagers host. Link/unlink actions, many-to-many, and nested resources land in subsequent milestones. PRD: docs/PRDs/PRD-relations-nesting.md.
  • Configurable record identifier field. A resource can now address its records by a property other than id — a primary key named something else, or a natural key such as a slug for human-readable URLs — by overriding the new AdminResource::getIdentifierField(): string (defaults to 'id'). The field is used both to read a record's identifier (building its row/view/edit URLs) and to resolve a record back from a URL, honouring scopeQuery(). A UUID/ULID key named $id already worked and still needs no configuration. (API) DataProviderInterface::find() gains a trailing string $idField = 'id' parameter — backward-compatible for callers, but a signature change for any third-party provider implementation. The Doctrine adapter keeps the fast identity-map path for primary-key lookups and queries WHERE <field> = :id for a custom field; the array adapter matches on the given field.
  • Record View (read-only) pages — core (VIEW-01..04, VIEW-11, VIEW-13, VIEW-15, VIEW-18). A resource can expose a read-only View screen for one record at the bare URL /{resource}/{id}, opt-in by registering a 'view' page (Atrium\Page\ViewPage, with an Edit header link). Content is declared with AdminResource::view(Schema) — a schema of read-only entry components that live in the same layout tree as form fields; if a resource defines no view(), the screen falls back to its form() fields rendered read-only. Ships the Atrium\View\Entry base (the full shared configuration surface: labelling, layout/align, record-aware visible/hidden, state/getStateUsing/ formatStateUsing/default/placeholder, tooltip/helperText/hint, icon, url, inline actions) and Atrium\View\TextEntry (badge, colour, money, date/time, numeric, limit/words, prefix/suffix, html, lists, copyable). Reuses the existing view/canView($record) ability; adds PageContext::viewUrl(). The remaining entry types (Icon/Image/Color/KeyValue/Repeatable/Code), view-screen widget bands, ViewAction + clickable rows, and docs land in the following milestones. PRD: docs/PRDs/PRD-record-view.md.
  • Record View entry family (VIEW-05..08, VIEW-10). Five more read-only entries alongside TextEntry, each sharing the full base configuration surface: Atrium\View\IconEntry (value as an icon; boolean() true/false ticks, color(), size()), ImageEntry (image/avatar; circular()/square(), imageSize()/imageWidth()/imageHeight(), defaultImageUrl(), with URL-scheme sanitisation), ColorEntry (a sanitised colour swatch, copyable()), KeyValueEntry (a 1-D array/JSON map as a key→value table, keyLabel()/valueLabel()) and CodeEntry (a monospace, escaped code block; language(), copyable()). The semantic colour vocabulary is shared with Content\Text / Table\Column via a common ResolvesColor concern. Docs: docs/integration-guide/pages/view.md.
  • Record View — RepeatableEntry (VIEW-09). Repeats a nested entry schema once per item of a relation / array attribute (a Doctrine collection, an array of entities, or an array of maps), binding each item as the record for the nested entries — schema(), columns(), grid() and contained(). It re-enters the shared layout renderer, so nested containers and nested repeatables compose. This completes the entry family.
  • Record View — navigation, Delete and widget bands (VIEW-12, VIEW-16, VIEW-17). When a resource has a View screen, its list rows link to it by default (a stretched overlay link; the row's buttons stay clickable), with a configurable target — TableConfiguration::recordUrl() takes 'view' (default), 'edit', a fn (object $record): ?string, or null to disable. Adds Atrium\Table\Action\ViewAction (the bare-record link, gated by view) and ActionContext::recordRootUrl(). The View screen's header now runs its Delete server action through the shared confirm → delete plumbing (hosted by a small RecordActions Live Component, so the otherwise-static screen needs no client JS). ViewPage::headerWidgets()/footerWidgets() add record-scoped widget bands above/below the entries (the widget context gains recordId), reusing the list/dashboard widget mechanism. Docs: docs/integration-guide/pages/view.md.

Changed

  • Hardening from the full-solution code review. Several defense-in-depth fixes, none affecting a correctly-configured Doctrine install:

    • Record screens fail closed without a data provider. On a no-Doctrine install (DataProviderInterface absent), edit/view and their nested variants now return 404 instead of rendering form/view chrome that could neither load nor authorize its record. create/list (which need no record) are unaffected.
    • Form::save() authorizes before developer hooks. canCreate()/canEdit() now run before mutateFormDataBeforeValidate()/afterValidate(), so a forged Live-action POST can no longer trigger those side-effect hooks unauthorized.
    • Action URLs are scheme-guarded. A custom Action::url(fn …) result is now passed through the same javascript:/data:-rejecting guard as table row URLs and view entries before reaching an href.
    • Many-to-many adapter parity. The Doctrine attach() is now idempotent (no duplicate pivot row / unique-constraint 500), its pivot lookup quotes identifiers (reserved-word/portability safe), and the array adapter matches pivot ids by strict, type-coerced comparison — both adapters now agree.
    • TextEntry::lineClamp() works. The line-clamp-N utilities are now compiled into the shipped stylesheet (previously purged, so the class had no effect). Relation/confirmation modals and the relation tab strip gained aria-labelledby / aria-selected / role="tabpanel".
  • path_prefix is now authoritative for route matching, not just link generation. The parametric routes (config/routes.php) previously hardcoded /admin, while path_prefix only changed the URLs the panel generated — so setting it to anything else broke the panel (links pointed somewhere the routes did not match). The routes now mount under the %atrium.path_prefix% parameter, so a single config value relocates both matching and generation together. Setting path_prefix: '/administrator' now actually serves the panel there; the default stays /admin and the route import is unchanged. Subdomain hosting (admin.example.com) is documented via the route import's host: option. Docs: docs/integration-guide/panel/customization.md#changing-the-url-the-panel-lives-at.

  • Icons now render through Symfony UX Icons. The panel's hardcoded name → SVG path Twig map ([@Atrium](https://github.com/Atrium)/icon.html.twig, ~18 heroicons-outline glyphs) is replaced by a shipped Lucide set registered under the atrium: icon-set prefix (via the bundle's prependExtension()), rendered with the ux_icon() function and an internal atrium_icon() helper that defaults bare names to the atrium: set. Icon names anywhere in the panel — getNavigationIcon(), Action::icon(), tab/step icons — now also accept any Iconify icon by passing a namespaced name (lucide:rocket, mdi:home, …); bare names resolve to the built-in set and an unknown one degrades to a neutral placeholder instead of erroring. The panel's glyphs change appearance (heroicons → Lucide). New required dependency: symfony/ux-icons. The [@Atrium](https://github.com/Atrium)/icon.html.twig template is removed — apps that overrode it should instead alias or repoint the atrium icon set in config/packages/ux_icons.yaml. Docs: docs/integration-guide/panel/customization.md#icons.

v0.1.0

First tagged release of Atrium — a PHP-configured, modular admin-panel framework for Symfony, powered by Live Components.

Highlights

  • Resources, list tables (columns, relation columns, filters, sorting, pagination, record/bulk actions, scoping, empty states)
  • Forms (schema + fields, layout components, tabs & wizards, validation, reactivity, custom field types) with atomic saves and lifecycle hooks
  • Pages as screen descriptors — headings, page-owned header actions, post-save redirects
  • Dashboards & widgets (StatsWidget, ChartWidget via UX Chart.js) and list-screen widget bands
  • Backend-agnostic data layer (DataProviderInterface/DataWriterInterface) with a Doctrine adapter

Pre-1.0: usable, but the public API may still change in a 0.x minor. See CHANGELOG.md for the full surface.

Install: composer require atriumphp/atrium:^0.1 (once registered on Packagist).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
andydefer/laravel-cluster
testo/fiber
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor
spatie/laravel-javascript-views