定制 gsebastiao/laravel-dynamic-menu 二次开发

按需修改功能、优化性能、对接业务系统,提供一站式技术支持

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

gsebastiao/laravel-dynamic-menu

Composer 安装命令:

composer require gsebastiao/laravel-dynamic-menu

包简介

Menus dinâmicos e hierárquicos (N níveis) para Laravel, com controlo de permissões flexível (none | string | id) e cache. 100% independente de qualquer pacote de permissões.

README 文档

README

Menus dinâmicos e hierárquicos (N níveis) para Laravel, com controlo de permissões flexível e cache para leitura rápida sem joins.

O pacote é 100% independente: não depende de Spatie nem de nenhum outro pacote de permissões. Podes usá-lo num projeto simples sem permissões, com permissões por string, ou apontando para qualquer tabela de permissões do teu projeto.

tests Latest Version License

Índice

Requisitos

Pacote Versão
PHP ^8.2
Laravel 11, 12 ou 13

Nota sobre Laravel 13 e PHP: o Laravel 13 exige PHP 8.3 no mínimo. Além disso, algumas versões de patch (13.3+) puxam componentes do Symfony 8 que exigem PHP 8.4. Se estiveres em PHP 8.3, considera fixar laravel/framework:^13.0 <13.3 até poderes atualizar o runtime. Em PHP 8.2 usa Laravel 11 ou 12.

Instalação

composer require gsebastiao/laravel-dynamic-menu

Publica a configuração (opcional, mas recomendado):

php artisan vendor:publish --tag=dynamic-menu-config

Corre as migrations (a tabela menu_items é registada automaticamente pelo pacote):

php artisan migrate

Se preferires versionar/editar a migration no teu projeto:

php artisan vendor:publish --tag=dynamic-menu-migrations

O Service Provider e a Facade Menu são registados automaticamente via package auto-discovery.

Conceito central: os três modos de permissão

Cada item de menu tem uma única coluna permission (nullable). Como ela é interpretada depende do permission_mode no config:

Modo O que guardas em permission Quando usar
none (ignorado) Projeto simples, sem permissões. É o default.
string Texto, ex.: user.create Queres permissões mas sem depender de FK.
id Um id, ex.: 42 Queres apontar para a tua tabela de permissões.

A mesma coluna carrega ora uma string ora um id — o config é que decide a leitura. Simples e flexível.

Configuração

Ficheiro config/dynamic-menu.php (resumo dos campos principais):

return [
    // 'none' | 'string' | 'id'
    'permission_mode' => env('DYNAMIC_MENU_PERMISSION_MODE', 'none'),

    // Usado apenas no modo 'id'. Aponta para QUALQUER tabela do teu projeto.
    'resolver' => [
        'table'  => 'permissions',
        'key'    => 'id',
        'column' => 'name',
    ],

    // Callback opcional que devolve as permissões do utilizador.
    // null => o pacote tenta $user->getAllPermissions() e faz fallback para [].
    'user_permissions' => null,

    'cache' => [
        'enabled' => true,
        'store'   => null,   // null = store default (podes pôr 'redis')
        'key'     => 'dynamic_menu',
        'ttl'     => null,   // null = forever
    ],

    'table' => 'menu_items',
    'model' => \Gsebastiao\DynamicMenu\Models\MenuItem::class,
];

Estrutura de um item de menu

Campo Tipo Descrição
id bigint PK
parent_id bigint, null Pai na hierarquia (N níveis)
name string Identificador interno/máquina (ex.: admin.users)
label string Texto exibido
description string, null Descrição opcional
route string, null Nome de rota, URL ou caminho
icon string, null Ícone
permission string, null Permissão (interpretação depende do modo)
order int Ordenação
target string, null _self, _blank, etc.
is_active bool Ativo/inativo
is_separator bool Item separador (linha)
badge string, null Badge (ex.: novo ou um contador)
params json, null Parâmetros de rota ou metadados
timestamps created_at / updated_at
deleted_at Soft delete

Uso

Ler a árvore completa

Devolve todos os itens ativos, já aninhados, servidos da cache (sem joins):

use Gsebastiao\DynamicMenu\Facades\Menu;

$tree = Menu::tree();
// Collection de arrays; cada nó tem uma chave 'children' (array)

Filtrar pela permissão do utilizador

Devolve apenas os itens que o utilizador pode ver, de acordo com o modo configurado. Um pai é mantido se tiver filhos visíveis (evita "buracos" na navegação):

// Utilizador autenticado
$menu = Menu::forUser();

// Um utilizador específico
$menu = Menu::forUser($user);

// Ou passando as permissões diretamente
$menu = Menu::forUser(null, ['user.create', 'admin.access']);

No modo none, forUser() devolve simplesmente a árvore completa.

Renderizar em Blade

Cada nó é um array. Exemplo recursivo simples:

{{-- resources/views/partials/menu.blade.php --}}
<ul>
    @foreach ($items as $item)
        @if ($item['is_separator'])
            <li class="separator"><hr></li>
        @else
            <li>
                <a href="{{ $item['route'] ? (\Illuminate\Support\Str::startsWith($item['route'], ['http', '/']) ? $item['route'] : route($item['route'], $item['params'] ?? [])) : '#' }}"
                   target="{{ $item['target'] ?? '_self' }}">
                    @if ($item['icon']) <i class="icon-{{ $item['icon'] }}"></i> @endif
                    {{ $item['label'] }}
                    @if ($item['badge']) <span class="badge">{{ $item['badge'] }}</span> @endif
                </a>

                @if (!empty($item['children']))
                    @include('partials.menu', ['items' => $item['children']])
                @endif
            </li>
        @endif
    @endforeach
</ul>
{{-- No layout --}}
@include('partials.menu', ['items' => \Gsebastiao\DynamicMenu\Facades\Menu::forUser()])

Criar itens

use Gsebastiao\DynamicMenu\Models\MenuItem;

$admin = MenuItem::create([
    'name'       => 'admin',
    'label'      => 'Administração',
    'icon'       => 'shield',
    'order'      => 1,
    'permission' => 'admin.access', // no modo 'id' seria o id, ex.: '42'
]);

MenuItem::create([
    'parent_id'  => $admin->id,
    'name'       => 'admin.users',
    'label'      => 'Utilizadores',
    'route'      => 'admin.users.index',
    'order'      => 1,
    'permission' => 'users.view',
    'badge'      => 'novo',
    'params'     => ['tab' => 'active'],
]);

A cache é invalidada automaticamente sempre que gravas, apagas ou restauras um item.

Modos de permissão em detalhe

Modo none (default)

Nada a configurar. O campo permission é ignorado, Menu::tree() e Menu::forUser() devolvem tudo, e o middleware deixa passar sempre. Ideal para projetos simples.

// config/dynamic-menu.php
'permission_mode' => 'none',

Modo string

Guardas a permissão como texto. Por omissão, o pacote obtém as permissões do utilizador via $user->getAllPermissions() (compatível com vários setups, incluindo Spatie) e compara as strings. Podes personalizar com o callback user_permissions:

'permission_mode' => 'string',

'user_permissions' => function ($user) {
    // devolve um array/Collection de strings
    return $user?->permissions()->pluck('name')->all() ?? [];
},
$menu = Menu::forUser($user); // já filtrado pelas permissões do $user

Modo id

Guardas o id da permissão (como texto) e apontas o resolver para a tua tabela. O pacote resolve o id contra ela quando precisa do rótulo legível, e compara ids na filtragem:

'permission_mode' => 'id',

'resolver' => [
    'table'  => 'permissions', // a TUA tabela
    'key'    => 'id',
    'column' => 'name',
],

'user_permissions' => function ($user) {
    // devolve os IDS das permissões do utilizador
    return $user?->permissions()->pluck('id')->all() ?? [];
},

Obter o rótulo legível de um item:

$item->resolvedPermissionLabel(); // ex.: 'gerir.tudo' (lido da tua tabela)

Middleware

O pacote regista o alias menu.permission. Protege rotas exigindo uma permissão:

Route::get('/admin', [AdminController::class, 'index'])
    ->middleware('menu.permission:admin.access');
  • No modo none, o middleware é um no-op (deixa passar sempre) — o mesmo código funciona em projetos com e sem permissões.
  • Nos modos string/id, devolve 403 se o utilizador não tiver a permissão indicada.

Cache

A árvore é lida da cache, sem joins. Configura o store em config/dynamic-menu.php:

'cache' => [
    'enabled' => true,
    'store'   => 'redis', // ou null para o default, 'file', etc.
    'ttl'     => 3600,    // segundos; null = forever
],

A cache é invalidada automaticamente em saved / deleted / restored do model. Para gerir manualmente:

# Reconstruir a cache
php artisan dynamic-menu:cache

# Apenas limpar
php artisan dynamic-menu:cache --flush

Ou por código:

Menu::rebuildCache();
Menu::flushCache();

Seeder

O pacote inclui um seeder de exemplo com uma hierarquia de várias profundidades. Publica-o e adapta:

php artisan vendor:publish --tag=dynamic-menu-seeders

Depois corre:

// database/seeders/DatabaseSeeder.php
$this->call(\Database\Seeders\MenuItemsSeeder::class);
php artisan db:seed --class="Database\\Seeders\\MenuItemsSeeder"

Ou usa diretamente o seeder do pacote sem publicar:

php artisan db:seed --class="Gsebastiao\\DynamicMenu\\Database\\Seeders\\MenuItemsSeeder"

Testes

O pacote usa Pest com Orchestra Testbench.

composer install
vendor/bin/pest

A suite cobre: montagem da árvore em N níveis, ordenação, cache e sua invalidação, filtragem por permissão nos três modos, resolução via tabela no modo id, o middleware e o comando artisan.

Licença

MIT. Ver LICENSE.md.

统计信息

  • 总下载量: 1
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-10

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固