iviphp / admin
Admin pages, resources and navigation for the IviPHP ecosystem.
Requires
- php: >=8.2
- iviphp/auth: ^0.1
- iviphp/contracts: ^0.1
- iviphp/http: ^0.1
- iviphp/support: ^0.1
- iviphp/view: ^0.1
This package is not auto-updated.
Last update: 2026-07-21 22:24:21 UTC
README
Administrative pages, resources, access control and navigation for the IviPHP ecosystem.
iviphp/admin provides a framework-independent foundation for building administration panels without coupling the application to a specific router, ORM, database or frontend library.
Requirements
- PHP 8.2 or later
iviphp/authiviphp/contractsiviphp/httpiviphp/supportiviphp/view
Installation
composer require iviphp/admin
Features
- Standalone administrative pages
- Administrative resources with multiple pages
- Authentication-aware access rules
- Resource-level and page-level authorization
- Immutable page and resource definitions
- Navigation registration and grouping
- Route-path resolution
- Navigation filtering by authenticated user
- Custom navigation entries
- View rendering through
iviphp/view - Framework-independent architecture
- Safe diagnostic exceptions
Core concepts
Page
An admin page represents one administrative screen.
A page defines:
- a unique name;
- a title;
- a route path;
- a view;
- view data;
- an access rule;
- navigation metadata.
Examples include dashboards, settings pages and reports.
Resource
An admin resource groups pages related to one application concept.
Examples include:
- users;
- projects;
- orders;
- products;
- system settings.
A resource may define its own access rule and navigation entry.
Navigation
Navigation stores immutable menu items and provides:
- unique-name validation;
- route conflict detection;
- deterministic sorting;
- grouping;
- exact and prefix route matching.
Admin manager
AdminManager coordinates:
- page registration;
- resource registration;
- access control;
- route resolution;
- navigation;
- rendering.
Admin API
Admin is the public application-facing wrapper around AdminManager.
Creating a page
<?php declare(strict_types=1); use Ivi\Admin\Pages\AdminPage; $dashboard = new AdminPage( name: 'dashboard', title: 'Dashboard', path: '/admin', view: 'admin.dashboard', data: [ 'heading' => 'Administration', ], navigationLabel: 'Dashboard', navigationGroup: 'General', navigationIcon: 'dashboard', navigationOrder: 10 );
The page is immutable after construction.
Lazy page data
Page data may be provided through a closure.
<?php declare(strict_types=1); use Ivi\Admin\Pages\AdminPage; $page = new AdminPage( name: 'reports', title: 'Reports', path: '/admin/reports', view: 'admin.reports', data: static function (): array { return [ 'generatedAt' => new DateTimeImmutable(), ]; } );
The resolver is executed whenever data() is called.
It must return an array.
Page access rules
A page may use a fixed boolean:
<?php declare(strict_types=1); use Ivi\Admin\Pages\AdminPage; $page = new AdminPage( name: 'public.status', title: 'System Status', path: '/admin/status', view: 'admin.status', access: true );
It may also use an access resolver:
<?php declare(strict_types=1); use Ivi\Admin\Pages\AdminPage; use Ivi\Auth\Contracts\AuthenticatableInterface; $page = new AdminPage( name: 'system.settings', title: 'System Settings', path: '/admin/settings', view: 'admin.settings', access: static function ( ?AuthenticatableInterface $user ): bool { if ($user === null) { return false; } return $user->authIdentifier() === 'administrator'; } );
A null identity represents an unauthenticated request.
Creating a resource
<?php declare(strict_types=1); use Ivi\Admin\Pages\AdminPage; use Ivi\Admin\Resources\AdminResource; $listUsers = new AdminPage( name: 'users.index', title: 'Users', path: '/admin/users', view: 'admin.users.index', registerNavigation: false ); $createUser = new AdminPage( name: 'users.create', title: 'Create User', path: '/admin/users/create', view: 'admin.users.create', registerNavigation: false ); $users = new AdminResource( name: 'users', label: 'User', pluralLabel: 'Users', path: '/admin/users', pages: [ $listUsers, $createUser, ], navigationLabel: 'Users', navigationGroup: 'Management', navigationIcon: 'users', navigationOrder: 20 );
All resource pages must use paths inside the resource base path.
For example, a resource using /admin/users may contain:
/admin/users
/admin/users/create
/admin/users/42
/admin/users/42/edit
Resource access rules
Resource access is evaluated before page access.
<?php declare(strict_types=1); use Ivi\Admin\Resources\AdminResource; use Ivi\Auth\Contracts\AuthenticatableInterface; $resource = new AdminResource( name: 'users', label: 'User', pluralLabel: 'Users', path: '/admin/users', pages: [], access: static function ( ?AuthenticatableInterface $user ): bool { return $user !== null; } );
A user must pass both the resource access rule and the page access rule.
Creating the admin manager
<?php declare(strict_types=1); use Ivi\Admin\Admin; use Ivi\Admin\AdminManager; /** @var \Ivi\View\ViewInterface $view */ /** @var \Ivi\Auth\Auth $auth */ $manager = new AdminManager( view: $view, auth: $auth ); $admin = new Admin($manager);
Authentication is optional:
$manager = new AdminManager( view: $view );
Without an authentication service, the current user is always null unless an identity is passed explicitly.
Registering pages
$admin->registerPage($dashboard);
Determine whether a page exists:
if ($admin->hasPage('dashboard')) { $page = $admin->page('dashboard'); }
Find a page by route path:
$page = $admin->pageByPath('/admin');
Replace a standalone page:
$admin->replacePage($updatedDashboard);
Remove a standalone page:
$admin->forgetPage('dashboard');
Resource-owned pages must be removed with their resource.
Registering resources
$admin->registerResource($users);
Retrieve a resource:
$resource = $admin->resource('users');
Find a resource by its exact base path:
$resource = $admin->resourceByPath( '/admin/users' );
Match the most specific resource for a route:
$resource = $admin->matchResource( '/admin/users/42/edit' );
Replace a resource:
$admin->replaceResource($updatedUsers);
Remove a resource and its pages:
$admin->forgetResource('users');
Rendering pages
Render a page by name:
$html = $admin->render('dashboard');
Pass additional data:
$html = $admin->render( 'dashboard', [ 'message' => 'Welcome back.', ] );
Additional data overrides data defined by the page.
Render from a route path:
$html = $admin->renderPath( '/admin/users' );
The manager injects an adminContext variable into every rendered page.
[
'page' => $page,
'resource' => $resource,
'navigation' => $navigation,
'user' => $user,
'manager' => $manager,
]
The adminContext variable cannot be overridden by page data or caller-provided data.
Checking access
Check access using the current authenticated identity:
if ($admin->canAccessPage('dashboard')) { // The current user may access the page. }
Check access for an explicit identity:
if ($admin->canAccessPage('dashboard', $user)) { // The supplied user may access the page. }
Check resource access:
if ($admin->canAccessResource('users', $user)) { // The supplied user may access the resource. }
Rendering a denied page throws AdminException.
Navigation
Retrieve navigation visible to the current authenticated identity:
$navigation = $admin->navigation(); foreach ($navigation->all() as $item) { echo $item->label(); echo $item->path(); }
Retrieve navigation for an explicit identity:
$navigation = $admin->navigation($user);
The returned navigation contains only pages and resources accessible to that identity.
Navigation groups
$groups = $admin ->navigation() ->grouped(); foreach ($groups as $group => $items) { echo $group === '' ? 'General' : $group; foreach ($items as $item) { echo $item->label(); } }
Retrieve one group:
$management = $admin ->navigation() ->group('Management');
Ungrouped entries are retrieved with null:
$general = $admin ->navigation() ->group(null);
Active navigation item
Find an exact or nested route match:
$item = $admin ->navigation() ->match('/admin/users/42/edit'); if ($item !== null) { echo $item->name(); }
Prefix matching returns the item with the longest matching path.
Custom navigation items
<?php declare(strict_types=1); use Ivi\Admin\Navigation\MenuItem; $admin->registerNavigationItem( new MenuItem( name: 'documentation', label: 'Documentation', path: '/admin/documentation', group: 'Help', icon: 'book', order: 100, metadata: [ 'external' => false, ] ) );
Custom items without page or resource metadata are not filtered by admin access rules.
Replace an existing navigation entry:
$admin->registerNavigationItem( $replacement, replace: true );
Menu item metadata
$item = new MenuItem( name: 'system.health', label: 'System Health', path: '/admin/system/health', metadata: [ 'badge' => 'Healthy', 'section' => 'system', ] );
Read metadata:
if ($item->hasMetadata('badge')) { echo $item->metadataValue('badge'); }
Export the item:
$data = $item->toArray();
Navigation ordering
Navigation entries are sorted by:
- group;
- order;
- label;
- name.
Lower order values appear before higher values.
new MenuItem( name: 'dashboard', label: 'Dashboard', path: '/admin', order: 10 ); new MenuItem( name: 'users', label: 'Users', path: '/admin/users', order: 20 );
The dashboard appears before users.
Page interface
Custom page implementations may implement:
Ivi\Admin\Contracts\AdminPageInterface
Required methods:
public function name(): string; public function title(): string; public function path(): string; public function view(): string; public function data(): array; public function canAccess( ?AuthenticatableInterface $user ): bool; public function shouldRegisterNavigation(): bool; public function navigationLabel(): string; public function navigationGroup(): ?string; public function navigationIcon(): ?string; public function navigationOrder(): int;
Resource interface
Custom resource implementations may implement:
Ivi\Admin\Contracts\AdminResourceInterface
Required methods:
public function name(): string; public function label(): string; public function pluralLabel(): string; public function path(): string; public function pages(): array; public function canAccess( ?AuthenticatableInterface $user ): bool; public function shouldRegisterNavigation(): bool; public function navigationLabel(): string; public function navigationGroup(): ?string; public function navigationIcon(): ?string; public function navigationOrder(): int; public function metadata(): array;
Exceptions
Administrative failures are represented by:
Ivi\Admin\Exceptions\AdminException
Examples include:
- duplicate page names;
- duplicate resource names;
- duplicate navigation items;
- conflicting route paths;
- invalid page or resource names;
- invalid configuration;
- denied access;
- rendering failures.
<?php declare(strict_types=1); use Ivi\Admin\Exceptions\AdminException; try { $html = $admin->render('dashboard'); } catch (AdminException $exception) { echo $exception->getMessage(); $context = $exception->context(); }
Exception context intentionally excludes authenticated-user details, passwords and page data.
Resetting the admin registry
Remove all registered pages, resources and navigation items:
$admin->flush();
The configured view and authentication services remain attached to the manager.
Design principles
iviphp/admin follows these principles:
- no dependency on a specific router;
- no dependency on an ORM;
- no dependency on a database;
- no direct HTML generation;
- explicit access rules;
- immutable definitions where practical;
- predictable route and navigation behavior;
- safe exception context;
- compatibility with application-specific admin interfaces.
Security
Applications should still enforce authorization at the controller, service and domain layers.
Admin navigation visibility must not be treated as the only security boundary.
Do not place secrets, passwords, tokens or private user data inside:
- page metadata;
- resource metadata;
- navigation metadata;
- exception context.
Views should escape untrusted output according to the rendering context.
License
Ivi Admin is open-source software released under the MIT License.
Maintainer
Maintained by Gaspard Kirira and Softadastra.