Contenuti




PHP: menu HTML dinamico

Menu di navigazione avanzati con template, breadcrumbs e autorizzazioni


PHP: menu HTML dinamico e navigazione avanzata

La creazione di menu di navigazione dinamici è una competenza fondamentale nello sviluppo web moderno. Un sistema di navigazione ben progettato migliora l’esperienza utente e facilita la manutenzione del codice. In questa guida vedrai come sviluppare sistemi di navigazione avanzati con PHP, dalla struttura base ai pattern enterprise.

In questo articolo
  • Architettura modulare per sistemi di navigazione scalabili
  • Template engine avanzati con cache e performance optimization
  • Menu multi-livello con breadcrumbs e navigazione contestuale
  • Sistemi di autorizzazione e permessi utente
  • Responsive design e accessibilità per tutti i dispositivi
  • SEO optimization e strutture dati per motori di ricerca
  • Best practices per performance e sicurezza

Indice della Guida

Parte I - Fondamenti

  1. Architettura del Sistema
  2. Template Engine Base
  3. Navigazione Dinamica

Parte II - Sviluppo avanzato

  1. Menu Multi-Livello
  2. Sistema di Autorizzazioni
  3. Breadcrumbs e Navigazione Contestuale

Parte III - Design e UX

  1. Responsive Design con Bootstrap
  2. Accessibilità e Performance
  3. SEO e Strutture Dati

Parte IV - Funzionalita enterprise

  1. Cache e Ottimizzazioni
  2. Testing e Quality Assurance
  3. Deployment e Best Practices

Architettura del Sistema

Struttura del Progetto Enterprise

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
navigation-system/
├── src/
│   ├── Core/
│   │   ├── MenuManager.php
│   │   ├── NavigationRenderer.php
│   │   └── PermissionManager.php
│   ├── Templates/
│   │   ├── layouts/
│   │   │   ├── main.php
│   │   │   └── admin.php
│   │   ├── menus/
│   │   │   ├── primary.php
│   │   │   ├── sidebar.php
│   │   │   └── footer.php
│   │   └── breadcrumbs/
│   │       └── default.php
│   ├── Config/
│   │   ├── navigation.php
│   │   └── permissions.php
│   └── Assets/
│       ├── css/
│       ├── js/
│       └── images/
├── cache/
│   └── navigation/
├── logs/
├── tests/
│   ├── Unit/
│   └── Integration/
├── composer.json
└── README.md

Configurazione del Sistema di Navigazione

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<?php
// src/Config/navigation.php
/**
 * Configurazione completa del sistema di navigazione
 */

return [
    'main_menu' => [
        'home' => [
            'label' => 'Home',
            'url' => '/',
            'icon' => 'fas fa-home',
            'permissions' => ['public'],
            'meta' => [
                'title' => 'Homepage - Il nostro sito',
                'description' => 'Benvenuto nella homepage'
            ]
        ],
        'products' => [
            'label' => 'Prodotti',
            'url' => '/products',
            'icon' => 'fas fa-box',
            'permissions' => ['public'],
            'children' => [
                'category_a' => [
                    'label' => 'Categoria A',
                    'url' => '/products/category-a',
                    'permissions' => ['public']
                ],
                'category_b' => [
                    'label' => 'Categoria B',
                    'url' => '/products/category-b',
                    'permissions' => ['public']
                ]
            ]
        ],
        'services' => [
            'label' => 'Servizi',
            'url' => '/services',
            'icon' => 'fas fa-cogs',
            'permissions' => ['public'],
            'children' => [
                'consulting' => [
                    'label' => 'Consulenza',
                    'url' => '/services/consulting',
                    'permissions' => ['public']
                ],
                'support' => [
                    'label' => 'Supporto',
                    'url' => '/services/support',
                    'permissions' => ['authenticated']
                ]
            ]
        ],
        'admin' => [
            'label' => 'Amministrazione',
            'url' => '/admin',
            'icon' => 'fas fa-cog',
            'permissions' => ['admin', 'manager'],
            'children' => [
                'users' => [
                    'label' => 'Utenti',
                    'url' => '/admin/users',
                    'permissions' => ['admin']
                ],
                'settings' => [
                    'label' => 'Impostazioni',
                    'url' => '/admin/settings',
                    'permissions' => ['admin', 'manager']
                ],
                'reports' => [
                    'label' => 'Report',
                    'url' => '/admin/reports',
                    'permissions' => ['admin', 'manager', 'analyst']
                ]
            ]
        ]
    ],

    'sidebar_menu' => [
        'dashboard' => [
            'label' => 'Dashboard',
            'url' => '/dashboard',
            'icon' => 'fas fa-tachometer-alt',
            'permissions' => ['authenticated']
        ],
        'profile' => [
            'label' => 'Profilo',
            'url' => '/profile',
            'icon' => 'fas fa-user',
            'permissions' => ['authenticated']
        ]
    ],

    'footer_menu' => [
        'about' => [
            'label' => 'Chi Siamo',
            'url' => '/about',
            'permissions' => ['public']
        ],
        'contact' => [
            'label' => 'Contatti',
            'url' => '/contact',
            'permissions' => ['public']
        ],
        'privacy' => [
            'label' => 'Privacy Policy',
            'url' => '/privacy',
            'permissions' => ['public']
        ]
    ],

    'settings' => [
        'cache_enabled' => true,
        'cache_duration' => 3600, // 1 ora
        'highlight_current' => true,
        'generate_breadcrumbs' => true,
        'seo_optimization' => true,
        'accessibility_mode' => true
    ]
];

Template Engine Base

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<?php
// src/Core/MenuManager.php

class MenuManager
{
    private array $config;
    private ?PermissionManager $permissionManager;
    private array $cache = [];
    private string $currentUrl;

    public function __construct(array $config, ?PermissionManager $permissionManager = null)
    {
        $this->config = $config;
        $this->permissionManager = $permissionManager;
        $this->currentUrl = $this->getCurrentUrl();
    }

    /**
     * Genera un menu completo con permessi e cache
     */
    public function generateMenu(string $menuType = 'main_menu', ?User $user = null): array
    {
        $cacheKey = "menu_{$menuType}_" . ($user ? $user->getId() : 'guest');

        // Check cache
        if ($this->config['settings']['cache_enabled'] && isset($this->cache[$cacheKey])) {
            return $this->cache[$cacheKey];
        }

        $menuItems = $this->config[$menuType] ?? [];
        $filteredMenu = $this->filterMenuByPermissions($menuItems, $user);
        $processedMenu = $this->processMenuItems($filteredMenu);

        // Store in cache
        if ($this->config['settings']['cache_enabled']) {
            $this->cache[$cacheKey] = $processedMenu;
        }

        return $processedMenu;
    }

    /**
     * Filtra gli elementi del menu in base ai permessi utente
     */
    private function filterMenuByPermissions(array $menuItems, ?User $user): array
    {
        $filteredItems = [];

        foreach ($menuItems as $key => $item) {
            // Check permissions
            if ($this->hasPermission($item['permissions'] ?? ['public'], $user)) {
                // Process children if exist
                if (isset($item['children'])) {
                    $item['children'] = $this->filterMenuByPermissions($item['children'], $user);

                    // Remove parent if no children remain after filtering
                    if (empty($item['children'])) {
                        continue;
                    }
                }

                $filteredItems[$key] = $item;
            }
        }

        return $filteredItems;
    }

    /**
     * Processa gli elementi del menu aggiungendo stati e metadata
     */
    private function processMenuItems(array $menuItems): array
    {
        $processedItems = [];

        foreach ($menuItems as $key => $item) {
            $item['key'] = $key;
            $item['is_active'] = $this->isActiveItem($item);
            $item['is_current'] = $this->isCurrentPage($item['url']);
            $item['has_children'] = isset($item['children']) && !empty($item['children']);

            // Process children recursively
            if ($item['has_children']) {
                $item['children'] = $this->processMenuItems($item['children']);
                $item['has_active_child'] = $this->hasActiveChild($item['children']);
            }

            // Add CSS classes
            $item['css_classes'] = $this->generateCssClasses($item);

            $processedItems[] = $item;
        }

        return $processedItems;
    }

    /**
     * Verifica se l'utente ha i permessi richiesti
     */
    private function hasPermission(array $requiredPermissions, ?User $user): bool
    {
        if (in_array('public', $requiredPermissions)) {
            return true;
        }

        if (!$user) {
            return false;
        }

        if ($this->permissionManager) {
            return $this->permissionManager->userHasAnyPermission($user, $requiredPermissions);
        }

        // Fallback: check if user is authenticated for 'authenticated' permission
        return in_array('authenticated', $requiredPermissions) && $user->isAuthenticated();
    }

    /**
     * Determina se un elemento del menu è attivo
     */
    private function isActiveItem(array $item): bool
    {
        if ($this->isCurrentPage($item['url'])) {
            return true;
        }

        // Check if any child is active
        if (isset($item['children'])) {
            foreach ($item['children'] as $child) {
                if ($this->isActiveItem($child)) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * Verifica se l'URL corrisponde alla pagina corrente
     */
    private function isCurrentPage(string $url): bool
    {
        return $this->currentUrl === $url ||
               ($url !== '/' && strpos($this->currentUrl, $url) === 0);
    }

    /**
     * Verifica se ci sono figli attivi
     */
    private function hasActiveChild(array $children): bool
    {
        foreach ($children as $child) {
            if ($child['is_active']) {
                return true;
            }
        }
        return false;
    }

    /**
     * Genera le classi CSS per un elemento del menu
     */
    private function generateCssClasses(array $item): string
    {
        $classes = ['nav-item'];

        if ($item['is_current']) {
            $classes[] = 'current-page';
        }

        if ($item['is_active']) {
            $classes[] = 'active';
        }

        if ($item['has_children']) {
            $classes[] = 'has-dropdown';
        }

        if (isset($item['has_active_child']) && $item['has_active_child']) {
            $classes[] = 'has-active-child';
        }

        return implode(' ', $classes);
    }

    /**
     * Ottiene l'URL corrente
     */
    private function getCurrentUrl(): string
    {
        $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? 'https' : 'http';
        $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
        $uri = $_SERVER['REQUEST_URI'] ?? '/';

        // Rimuovi query string per il matching
        $uri = strtok($uri, '?');

        return parse_url($protocol . '://' . $host . $uri, PHP_URL_PATH) ?? '/';
    }

    /**
     * Genera breadcrumbs basati sul menu corrente
     */
    public function generateBreadcrumbs(string $menuType = 'main_menu', ?User $user = null): array
    {
        if (!$this->config['settings']['generate_breadcrumbs']) {
            return [];
        }

        $menu = $this->generateMenu($menuType, $user);
        $breadcrumbs = [];

        $this->findBreadcrumbPath($menu, $this->currentUrl, $breadcrumbs);

        // Add home if not already present
        if (!empty($breadcrumbs) && $breadcrumbs[0]['url'] !== '/') {
            array_unshift($breadcrumbs, [
                'label' => 'Home',
                'url' => '/',
                'is_current' => false
            ]);
        }

        // Mark last item as current
        if (!empty($breadcrumbs)) {
            $breadcrumbs[count($breadcrumbs) - 1]['is_current'] = true;
        }

        return $breadcrumbs;
    }

    /**
     * Trova ricorsivamente il percorso dei breadcrumbs
     */
    private function findBreadcrumbPath(array $menu, string $targetUrl, array &$path, array $currentPath = []): bool
    {
        foreach ($menu as $item) {
            $newPath = array_merge($currentPath, [
                'label' => $item['label'],
                'url' => $item['url']
            ]);

            if ($item['url'] === $targetUrl) {
                $path = $newPath;
                return true;
            }

            if (isset($item['children']) && !empty($item['children'])) {
                if ($this->findBreadcrumbPath($item['children'], $targetUrl, $path, $newPath)) {
                    return true;
                }
            }
        }

        return false;
    }
}
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
<?php
// src/Core/NavigationRenderer.php

class NavigationRenderer
{
    private MenuManager $menuManager;
    private string $templatePath;
    private array $globalVariables = [];

    public function __construct(MenuManager $menuManager, string $templatePath)
    {
        $this->menuManager = $menuManager;
        $this->templatePath = rtrim($templatePath, '/');
    }

    /**
     * Rende un menu utilizzando un template specifico
     */
    public function renderMenu(string $menuType, string $template = 'default', ?User $user = null): string
    {
        $menu = $this->menuManager->generateMenu($menuType, $user);
        $breadcrumbs = $this->menuManager->generateBreadcrumbs($menuType, $user);

        $templateFile = $this->getTemplatePath('menus', $template);

        return $this->renderTemplate($templateFile, [
            'menu' => $menu,
            'breadcrumbs' => $breadcrumbs,
            'menuType' => $menuType,
            'user' => $user
        ]);
    }

    /**
     * Rende breadcrumbs utilizzando un template
     */
    public function renderBreadcrumbs(string $menuType = 'main_menu', string $template = 'default', ?User $user = null): string
    {
        $breadcrumbs = $this->menuManager->generateBreadcrumbs($menuType, $user);

        if (empty($breadcrumbs)) {
            return '';
        }

        $templateFile = $this->getTemplatePath('breadcrumbs', $template);

        return $this->renderTemplate($templateFile, [
            'breadcrumbs' => $breadcrumbs,
            'user' => $user
        ]);
    }

    /**
     * Rende un template con variabili
     */
    public function renderTemplate(string $templateFile, array $variables = []): string
    {
        if (!file_exists($templateFile)) {
            throw new Exception("Template file not found: {$templateFile}");
        }

        // Merge global variables
        $variables = array_merge($this->globalVariables, $variables);

        // Extract variables to local scope
        extract($variables, EXTR_SKIP);

        // Start output buffering
        ob_start();

        try {
            include $templateFile;
            return ob_get_clean();
        } catch (Exception $e) {
            ob_end_clean();
            throw $e;
        }
    }

    /**
     * Imposta variabili globali disponibili in tutti i template
     */
    public function setGlobalVariable(string $name, $value): void
    {
        $this->globalVariables[$name] = $value;
    }

    /**
     * Ottiene il percorso completo di un template
     */
    private function getTemplatePath(string $type, string $template): string
    {
        return $this->templatePath . "/{$type}/{$template}.php";
    }

    /**
     * Helper per generare attributi HTML
     */
    public function renderAttributes(array $attributes): string
    {
        $html = [];

        foreach ($attributes as $key => $value) {
            if (is_bool($value)) {
                if ($value) {
                    $html[] = htmlspecialchars($key, ENT_QUOTES, 'UTF-8');
                }
            } elseif ($value !== null) {
                $html[] = htmlspecialchars($key, ENT_QUOTES, 'UTF-8') . '="' .
                         htmlspecialchars($value, ENT_QUOTES, 'UTF-8') . '"';
            }
        }

        return implode(' ', $html);
    }
}

Template Bootstrap Responsive

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
<?php
// src/Templates/menus/bootstrap-navbar.php
/**
 * Template Bootstrap 5 per menu di navigazione multi-livello
 */
?>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary" role="navigation" aria-label="Main navigation">
    <div class="container-fluid">
        <!-- Brand/Logo -->
        <a class="navbar-brand" href="/">
            <img src="/assets/images/logo.png" alt="Logo" width="30" height="24" class="d-inline-block align-text-top">
            Il Mio Sito
        </a>

        <!-- Mobile Toggle -->
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
                aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>

        <!-- Navigation Menu -->
        <div class="collapse navbar-collapse" id="navbarNav">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <?php foreach ($menu as $item): ?>
                    <li class="nav-item <?php echo $item['has_children'] ? 'dropdown' : ''; ?>">
                        <?php if ($item['has_children']): ?>
                            <!-- Dropdown Menu -->
                            <a class="nav-link dropdown-toggle <?php echo $item['css_classes']; ?>"
                               href="<?php echo htmlspecialchars($item['url']); ?>"
                               id="navbarDropdown<?php echo $item['key']; ?>"
                               role="button"
                               data-bs-toggle="dropdown"
                               aria-expanded="false"
                               <?php echo $item['is_current'] ? 'aria-current="page"' : ''; ?>>
                                <?php if (isset($item['icon'])): ?>
                                    <i class="<?php echo htmlspecialchars($item['icon']); ?>"></i>
                                <?php endif; ?>
                                <?php echo htmlspecialchars($item['label']); ?>
                            </a>

                            <!-- Dropdown Items -->
                            <ul class="dropdown-menu" aria-labelledby="navbarDropdown<?php echo $item['key']; ?>">
                                <?php foreach ($item['children'] as $child): ?>
                                    <li>
                                        <a class="dropdown-item <?php echo $child['css_classes']; ?>"
                                           href="<?php echo htmlspecialchars($child['url']); ?>"
                                           <?php echo $child['is_current'] ? 'aria-current="page"' : ''; ?>>
                                            <?php if (isset($child['icon'])): ?>
                                                <i class="<?php echo htmlspecialchars($child['icon']); ?>"></i>
                                            <?php endif; ?>
                                            <?php echo htmlspecialchars($child['label']); ?>
                                        </a>
                                    </li>

                                    <!-- Sub-children (3rd level) -->
                                    <?php if (isset($child['children']) && !empty($child['children'])): ?>
                                        <li><hr class="dropdown-divider"></li>
                                        <?php foreach ($child['children'] as $subchild): ?>
                                            <li>
                                                <a class="dropdown-item ps-4 <?php echo $subchild['css_classes']; ?>"
                                                   href="<?php echo htmlspecialchars($subchild['url']); ?>">
                                                    <small><?php echo htmlspecialchars($subchild['label']); ?></small>
                                                </a>
                                            </li>
                                        <?php endforeach; ?>
                                    <?php endif; ?>
                                <?php endforeach; ?>
                            </ul>
                        <?php else: ?>
                            <!-- Simple Link -->
                            <a class="nav-link <?php echo $item['css_classes']; ?>"
                               href="<?php echo htmlspecialchars($item['url']); ?>"
                               <?php echo $item['is_current'] ? 'aria-current="page"' : ''; ?>>
                                <?php if (isset($item['icon'])): ?>
                                    <i class="<?php echo htmlspecialchars($item['icon']); ?>"></i>
                                <?php endif; ?>
                                <?php echo htmlspecialchars($item['label']); ?>
                            </a>
                        <?php endif; ?>
                    </li>
                <?php endforeach; ?>
            </ul>

            <!-- User Menu -->
            <?php if ($user && $user->isAuthenticated()): ?>
                <ul class="navbar-nav">
                    <li class="nav-item dropdown">
                        <a class="nav-link dropdown-toggle" href="#" id="userDropdown" role="button"
                           data-bs-toggle="dropdown" aria-expanded="false">
                            <i class="fas fa-user-circle"></i>
                            <?php echo htmlspecialchars($user->getDisplayName()); ?>
                        </a>
                        <ul class="dropdown-menu dropdown-menu-end">
                            <li><a class="dropdown-item" href="/profile"><i class="fas fa-user"></i> Profilo</a></li>
                            <li><a class="dropdown-item" href="/settings"><i class="fas fa-cog"></i> Impostazioni</a></li>
                            <li><hr class="dropdown-divider"></li>
                            <li><a class="dropdown-item" href="/logout"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
                        </ul>
                    </li>
                </ul>
            <?php else: ?>
                <ul class="navbar-nav">
                    <li class="nav-item">
                        <a class="nav-link" href="/login">
                            <i class="fas fa-sign-in-alt"></i> Login
                        </a>
                    </li>
                </ul>
            <?php endif; ?>
        </div>
    </div>
</nav>

<!-- Breadcrumbs (optional) -->
<?php if (!empty($breadcrumbs) && count($breadcrumbs) > 1): ?>
    <nav aria-label="breadcrumb" class="bg-light py-2">
        <div class="container-fluid">
            <ol class="breadcrumb mb-0">
                <?php foreach ($breadcrumbs as $crumb): ?>
                    <li class="breadcrumb-item <?php echo $crumb['is_current'] ? 'active' : ''; ?>"
                        <?php echo $crumb['is_current'] ? 'aria-current="page"' : ''; ?>>
                        <?php if (!$crumb['is_current']): ?>
                            <a href="<?php echo htmlspecialchars($crumb['url']); ?>">
                                <?php echo htmlspecialchars($crumb['label']); ?>
                            </a>
                        <?php else: ?>
                            <?php echo htmlspecialchars($crumb['label']); ?>
                        <?php endif; ?>
                    </li>
                <?php endforeach; ?>
            </ol>
        </div>
    </nav>
<?php endif; ?>
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
<?php
// src/Templates/menus/sidebar.php
/**
 * Template per menu sidebar amministrativo
 */
?>
<div class="sidebar bg-dark" id="sidebar">
    <div class="sidebar-header">
        <h4 class="text-white text-center py-3">
            <i class="fas fa-cogs"></i> Admin Panel
        </h4>
    </div>

    <nav class="sidebar-nav">
        <ul class="nav flex-column">
            <?php foreach ($menu as $item): ?>
                <li class="nav-item">
                    <?php if ($item['has_children']): ?>
                        <!-- Collapsible Menu Item -->
                        <a class="nav-link d-flex align-items-center <?php echo $item['css_classes']; ?>"
                           href="#submenu<?php echo $item['key']; ?>"
                           data-bs-toggle="collapse"
                           aria-expanded="<?php echo $item['has_active_child'] ? 'true' : 'false'; ?>"
                           role="button">
                            <?php if (isset($item['icon'])): ?>
                                <i class="<?php echo htmlspecialchars($item['icon']); ?> me-2"></i>
                            <?php endif; ?>
                            <span><?php echo htmlspecialchars($item['label']); ?></span>
                            <i class="fas fa-chevron-down ms-auto"></i>
                        </a>

                        <!-- Submenu -->
                        <div class="collapse <?php echo $item['has_active_child'] ? 'show' : ''; ?>"
                             id="submenu<?php echo $item['key']; ?>">
                            <ul class="nav flex-column ms-3">
                                <?php foreach ($item['children'] as $child): ?>
                                    <li class="nav-item">
                                        <a class="nav-link d-flex align-items-center <?php echo $child['css_classes']; ?>"
                                           href="<?php echo htmlspecialchars($child['url']); ?>"
                                           <?php echo $child['is_current'] ? 'aria-current="page"' : ''; ?>>
                                            <?php if (isset($child['icon'])): ?>
                                                <i class="<?php echo htmlspecialchars($child['icon']); ?> me-2"></i>
                                            <?php endif; ?>
                                            <span><?php echo htmlspecialchars($child['label']); ?></span>

                                            <?php if (isset($child['badge'])): ?>
                                                <span class="badge bg-secondary ms-auto">
                                                    <?php echo htmlspecialchars($child['badge']); ?>
                                                </span>
                                            <?php endif; ?>
                                        </a>
                                    </li>
                                <?php endforeach; ?>
                            </ul>
                        </div>
                    <?php else: ?>
                        <!-- Simple Menu Item -->
                        <a class="nav-link d-flex align-items-center <?php echo $item['css_classes']; ?>"
                           href="<?php echo htmlspecialchars($item['url']); ?>"
                           <?php echo $item['is_current'] ? 'aria-current="page"' : ''; ?>>
                            <?php if (isset($item['icon'])): ?>
                                <i class="<?php echo htmlspecialchars($item['icon']); ?> me-2"></i>
                            <?php endif; ?>
                            <span><?php echo htmlspecialchars($item['label']); ?></span>

                            <?php if (isset($item['badge'])): ?>
                                <span class="badge bg-secondary ms-auto">
                                    <?php echo htmlspecialchars($item['badge']); ?>
                                </span>
                            <?php endif; ?>
                        </a>
                    <?php endif; ?>
                </li>
            <?php endforeach; ?>
        </ul>
    </nav>

    <!-- Sidebar Footer -->
    <div class="sidebar-footer mt-auto p-3">
        <?php if ($user && $user->isAuthenticated()): ?>
            <div class="user-info text-white">
                <div class="d-flex align-items-center">
                    <div class="avatar me-2">
                        <i class="fas fa-user-circle fa-lg"></i>
                    </div>
                    <div class="user-details">
                        <small class="d-block"><?php echo htmlspecialchars($user->getDisplayName()); ?></small>
                        <small class="text-muted"><?php echo htmlspecialchars($user->getRole()); ?></small>
                    </div>
                </div>
                <div class="mt-2">
                    <a href="/logout" class="btn btn-sm btn-outline-light">
                        <i class="fas fa-sign-out-alt"></i> Logout
                    </a>
                </div>
            </div>
        <?php endif; ?>
    </div>
</div>

<style>
.sidebar {
    min-height: 100vh;
    width: 250px;
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1000;
    transition: all 0.3s;
}

.sidebar .nav-link {
    color: #fff;
    padding: 0.75rem 1rem;
    border-bottom: 1px solid rgba(255,255,255,0.1);
    transition: all 0.2s;
}

.sidebar .nav-link:hover {
    background-color: rgba(255,255,255,0.1);
    color: #fff;
}

.sidebar .nav-link.active,
.sidebar .nav-link.current-page {
    background-color: rgba(255,255,255,0.2);
    color: #fff;
    font-weight: 600;
}

.sidebar-nav {
    max-height: calc(100vh - 200px);
    overflow-y: auto;
}

/* Responsive */
@media (max-width: 768px) {
    .sidebar {
        margin-left: -250px;
    }

    .sidebar.show {
        margin-left: 0;
    }
}
</style>

Sistema di Autorizzazioni

PermissionManager Class

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
<?php
// src/Core/PermissionManager.php

class PermissionManager
{
    private array $roleHierarchy;
    private array $permissionCache = [];

    public function __construct(array $roleHierarchy = [])
    {
        $this->roleHierarchy = $roleHierarchy ?: [
            'super_admin' => ['admin', 'manager', 'analyst', 'authenticated', 'public'],
            'admin' => ['manager', 'analyst', 'authenticated', 'public'],
            'manager' => ['analyst', 'authenticated', 'public'],
            'analyst' => ['authenticated', 'public'],
            'authenticated' => ['public'],
            'public' => []
        ];
    }

    /**
     * Verifica se un utente ha uno qualsiasi dei permessi richiesti
     */
    public function userHasAnyPermission(User $user, array $requiredPermissions): bool
    {
        $userPermissions = $this->getUserPermissions($user);

        foreach ($requiredPermissions as $permission) {
            if (in_array($permission, $userPermissions)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Verifica se un utente ha tutti i permessi richiesti
     */
    public function userHasAllPermissions(User $user, array $requiredPermissions): bool
    {
        $userPermissions = $this->getUserPermissions($user);

        foreach ($requiredPermissions as $permission) {
            if (!in_array($permission, $userPermissions)) {
                return false;
            }
        }

        return true;
    }

    /**
     * Ottiene tutti i permessi di un utente basati sui suoi ruoli
     */
    public function getUserPermissions(User $user): array
    {
        $userId = $user->getId();

        // Check cache
        if (isset($this->permissionCache[$userId])) {
            return $this->permissionCache[$userId];
        }

        $permissions = [];
        $userRoles = $user->getRoles();

        foreach ($userRoles as $role) {
            if (isset($this->roleHierarchy[$role])) {
                $permissions = array_merge($permissions, $this->roleHierarchy[$role]);
            }
            // Add the role itself as a permission
            $permissions[] = $role;
        }

        // Add authenticated permission if user is logged in
        if ($user->isAuthenticated()) {
            $permissions[] = 'authenticated';
        }

        // Always add public permission
        $permissions[] = 'public';

        // Remove duplicates
        $permissions = array_unique($permissions);

        // Cache result
        $this->permissionCache[$userId] = $permissions;

        return $permissions;
    }

    /**
     * Verifica se un ruolo include un altro ruolo
     */
    public function roleIncludes(string $role, string $targetRole): bool
    {
        if ($role === $targetRole) {
            return true;
        }

        if (!isset($this->roleHierarchy[$role])) {
            return false;
        }

        return in_array($targetRole, $this->roleHierarchy[$role]);
    }

    /**
     * Ottiene il livello gerarchico di un ruolo
     */
    public function getRoleLevel(string $role): int
    {
        $levels = [
            'super_admin' => 100,
            'admin' => 80,
            'manager' => 60,
            'analyst' => 40,
            'authenticated' => 20,
            'public' => 0
        ];

        return $levels[$role] ?? 0;
    }

    /**
     * Pulisce la cache dei permessi
     */
    public function clearPermissionCache(?int $userId = null): void
    {
        if ($userId) {
            unset($this->permissionCache[$userId]);
        } else {
            $this->permissionCache = [];
        }
    }
}

User Class con Sistema di Ruoli

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
<?php
// src/Core/User.php

class User
{
    private int $id;
    private string $username;
    private string $email;
    private array $roles;
    private bool $isAuthenticated;
    private array $metadata;

    public function __construct(int $id, string $username, string $email, array $roles = [], bool $isAuthenticated = false)
    {
        $this->id = $id;
        $this->username = $username;
        $this->email = $email;
        $this->roles = $roles;
        $this->isAuthenticated = $isAuthenticated;
        $this->metadata = [];
    }

    public function getId(): int
    {
        return $this->id;
    }

    public function getUsername(): string
    {
        return $this->username;
    }

    public function getEmail(): string
    {
        return $this->email;
    }

    public function getRoles(): array
    {
        return $this->roles;
    }

    public function hasRole(string $role): bool
    {
        return in_array($role, $this->roles);
    }

    public function getHighestRole(): ?string
    {
        $roleHierarchy = [
            'super_admin' => 100,
            'admin' => 80,
            'manager' => 60,
            'analyst' => 40,
            'authenticated' => 20,
            'public' => 0
        ];

        $highestLevel = -1;
        $highestRole = null;

        foreach ($this->roles as $role) {
            $level = $roleHierarchy[$role] ?? 0;
            if ($level > $highestLevel) {
                $highestLevel = $level;
                $highestRole = $role;
            }
        }

        return $highestRole;
    }

    public function getDisplayName(): string
    {
        return $this->metadata['display_name'] ?? $this->username;
    }

    public function isAuthenticated(): bool
    {
        return $this->isAuthenticated;
    }

    public function getRole(): string
    {
        return $this->getHighestRole() ?? 'guest';
    }

    public function setMetadata(string $key, $value): void
    {
        $this->metadata[$key] = $value;
    }

    public function getMetadata(string $key, $default = null)
    {
        return $this->metadata[$key] ?? $default;
    }
}

Template Breadcrumbs Avanzato

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
<?php
// src/Templates/breadcrumbs/default.php
/**
 * Template breadcrumbs con schema.org markup per SEO
 */
?>
<?php if (!empty($breadcrumbs) && count($breadcrumbs) > 1): ?>
<nav aria-label="breadcrumb" class="breadcrumb-nav">
    <ol class="breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
        <?php foreach ($breadcrumbs as $index => $crumb): ?>
            <li class="breadcrumb-item <?php echo $crumb['is_current'] ? 'active' : ''; ?>"
                itemprop="itemListElement"
                itemscope
                itemtype="https://schema.org/ListItem"
                <?php echo $crumb['is_current'] ? 'aria-current="page"' : ''; ?>>

                <?php if (!$crumb['is_current']): ?>
                    <a href="<?php echo htmlspecialchars($crumb['url']); ?>"
                       itemprop="item">
                        <span itemprop="name"><?php echo htmlspecialchars($crumb['label']); ?></span>
                    </a>
                <?php else: ?>
                    <span itemprop="name"><?php echo htmlspecialchars($crumb['label']); ?></span>
                <?php endif; ?>

                <meta itemprop="position" content="<?php echo $index + 1; ?>">
            </li>
        <?php endforeach; ?>
    </ol>
</nav>

<style>
.breadcrumb-nav {
    background: transparent;
    padding: 0.5rem 0;
}

.breadcrumb {
    background: rgba(255, 255, 255, 0.1);
    border-radius: 0.375rem;
    margin-bottom: 0;
    padding: 0.5rem 1rem;
}

.breadcrumb-item + .breadcrumb-item::before {
    content: "›";
    color: #6c757d;
    font-weight: bold;
}

.breadcrumb-item a {
    color: #0d6efd;
    text-decoration: none;
}

.breadcrumb-item a:hover {
    text-decoration: underline;
}

.breadcrumb-item.active {
    color: #6c757d;
    font-weight: 500;
}

@media (max-width: 576px) {
    .breadcrumb {
        font-size: 0.875rem;
        padding: 0.375rem 0.75rem;
    }
}
</style>
<?php endif; ?>

Performance e Cache

Sistema di Cache Avanzato

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
<?php
// src/Core/CacheManager.php

class CacheManager
{
    private string $cacheDir;
    private int $defaultTtl;

    public function __construct(string $cacheDir, int $defaultTtl = 3600)
    {
        $this->cacheDir = rtrim($cacheDir, '/');
        $this->defaultTtl = $defaultTtl;

        // Crea directory cache se non esiste
        if (!is_dir($this->cacheDir)) {
            mkdir($this->cacheDir, 0755, true);
        }
    }

    /**
     * Salva dati nella cache
     */
    public function set(string $key, $data, ?int $ttl = null): bool
    {
        $ttl = $ttl ?? $this->defaultTtl;
        $filePath = $this->getCacheFilePath($key);

        $cacheData = [
            'data' => $data,
            'expires' => time() + $ttl,
            'created' => time()
        ];

        return file_put_contents($filePath, serialize($cacheData)) !== false;
    }

    /**
     * Recupera dati dalla cache
     */
    public function get(string $key)
    {
        $filePath = $this->getCacheFilePath($key);

        if (!file_exists($filePath)) {
            return null;
        }

        $cacheData = unserialize(file_get_contents($filePath));

        if (!$cacheData || time() > $cacheData['expires']) {
            $this->delete($key);
            return null;
        }

        return $cacheData['data'];
    }

    /**
     * Verifica se una chiave esiste nella cache
     */
    public function has(string $key): bool
    {
        return $this->get($key) !== null;
    }

    /**
     * Elimina un elemento dalla cache
     */
    public function delete(string $key): bool
    {
        $filePath = $this->getCacheFilePath($key);

        if (file_exists($filePath)) {
            return unlink($filePath);
        }

        return true;
    }

    /**
     * Pulisce tutta la cache
     */
    public function clear(): bool
    {
        $files = glob($this->cacheDir . '/*.cache');

        foreach ($files as $file) {
            if (is_file($file)) {
                unlink($file);
            }
        }

        return true;
    }

    /**
     * Pulisce la cache scaduta
     */
    public function clearExpired(): int
    {
        $files = glob($this->cacheDir . '/*.cache');
        $deleted = 0;

        foreach ($files as $file) {
            if (is_file($file)) {
                $cacheData = unserialize(file_get_contents($file));

                if (!$cacheData || time() > $cacheData['expires']) {
                    unlink($file);
                    $deleted++;
                }
            }
        }

        return $deleted;
    }

    /**
     * Ottiene il percorso del file cache
     */
    private function getCacheFilePath(string $key): string
    {
        $hashedKey = md5($key);
        return $this->cacheDir . '/' . $hashedKey . '.cache';
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<?php
// Aggiunta al MenuManager per integrazione cache

class MenuManager
{
    private ?CacheManager $cacheManager;

    public function __construct(array $config, ?PermissionManager $permissionManager = null, ?CacheManager $cacheManager = null)
    {
        $this->config = $config;
        $this->permissionManager = $permissionManager;
        $this->cacheManager = $cacheManager;
        $this->currentUrl = $this->getCurrentUrl();
    }

    /**
     * Genera menu con cache avanzato
     */
    public function generateMenu(string $menuType = 'main_menu', ?User $user = null): array
    {
        if (!$this->config['settings']['cache_enabled'] || !$this->cacheManager) {
            return $this->buildMenu($menuType, $user);
        }

        $cacheKey = "menu_{$menuType}_" . ($user ? $user->getId() . '_' . md5(serialize($user->getRoles())) : 'guest');

        // Try to get from cache
        $cachedMenu = $this->cacheManager->get($cacheKey);

        if ($cachedMenu !== null) {
            // Update current page states (not cached as they change per request)
            return $this->updateCurrentPageStates($cachedMenu);
        }

        // Generate menu and cache it
        $menu = $this->buildMenu($menuType, $user);
        $menuToCache = $this->removeCurrentPageStates($menu);

        $this->cacheManager->set($cacheKey, $menuToCache, $this->config['settings']['cache_duration'] ?? 3600);

        return $menu;
    }

    /**
     * Aggiorna gli stati delle pagine correnti
     */
    private function updateCurrentPageStates(array $menu): array
    {
        return array_map(function ($item) {
            $item['is_active'] = $this->isActiveItem($item);
            $item['is_current'] = $this->isCurrentPage($item['url']);

            if (isset($item['children'])) {
                $item['children'] = $this->updateCurrentPageStates($item['children']);
                $item['has_active_child'] = $this->hasActiveChild($item['children']);
            }

            $item['css_classes'] = $this->generateCssClasses($item);

            return $item;
        }, $menu);
    }

    /**
     * Rimuove gli stati delle pagine correnti per il caching
     */
    private function removeCurrentPageStates(array $menu): array
    {
        return array_map(function ($item) {
            unset($item['is_active'], $item['is_current'], $item['has_active_child'], $item['css_classes']);

            if (isset($item['children'])) {
                $item['children'] = $this->removeCurrentPageStates($item['children']);
            }

            return $item;
        }, $menu);
    }

    /**
     * Invalida la cache per un tipo di menu
     */
    public function invalidateMenuCache(string $menuType): void
    {
        if (!$this->cacheManager) {
            return;
        }

        // Pattern per invalidare tutte le cache del menu type
        $this->cacheManager->delete("menu_{$menuType}_guest");

        // In una implementazione reale, dovresti invalidare anche le cache degli utenti
        // Questo richiede di tenere traccia delle chiavi cache generate
    }
}

Testing e Quality Assurance

Test di Unità per MenuManager

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
<?php
// tests/Unit/MenuManagerTest.php

use PHPUnit\Framework\TestCase;

class MenuManagerTest extends TestCase
{
    private MenuManager $menuManager;
    private array $testConfig;

    protected function setUp(): void
    {
        $this->testConfig = [
            'main_menu' => [
                'home' => [
                    'label' => 'Home',
                    'url' => '/',
                    'permissions' => ['public']
                ],
                'admin' => [
                    'label' => 'Admin',
                    'url' => '/admin',
                    'permissions' => ['admin'],
                    'children' => [
                        'users' => [
                            'label' => 'Users',
                            'url' => '/admin/users',
                            'permissions' => ['admin']
                        ]
                    ]
                ]
            ],
            'settings' => [
                'cache_enabled' => false,
                'highlight_current' => true,
                'generate_breadcrumbs' => true
            ]
        ];

        $this->menuManager = new MenuManager($this->testConfig);
    }

    public function testGenerateMenuForPublicUser(): void
    {
        $menu = $this->menuManager->generateMenu('main_menu');

        $this->assertCount(1, $menu); // Only public items
        $this->assertEquals('Home', $menu[0]['label']);
        $this->assertEquals('/', $menu[0]['url']);
    }

    public function testGenerateMenuForAdminUser(): void
    {
        $user = new User(1, 'admin', 'admin@test.com', ['admin'], true);
        $menu = $this->menuManager->generateMenu('main_menu', $user);

        $this->assertCount(2, $menu); // Public and admin items

        $adminItem = array_filter($menu, fn($item) => $item['label'] === 'Admin')[0];
        $this->assertTrue($adminItem['has_children']);
        $this->assertCount(1, $adminItem['children']);
    }

    public function testMenuItemStates(): void
    {
        // Simulate being on home page
        $_SERVER['REQUEST_URI'] = '/';

        $menu = $this->menuManager->generateMenu('main_menu');

        $homeItem = $menu[0];
        $this->assertTrue($homeItem['is_current']);
        $this->assertTrue($homeItem['is_active']);
        $this->assertStringContains('current-page', $homeItem['css_classes']);
    }

    public function testBreadcrumbGeneration(): void
    {
        // Simulate being on admin users page
        $_SERVER['REQUEST_URI'] = '/admin/users';

        $user = new User(1, 'admin', 'admin@test.com', ['admin'], true);
        $breadcrumbs = $this->menuManager->generateBreadcrumbs('main_menu', $user);

        $this->assertCount(3, $breadcrumbs); // Home > Admin > Users
        $this->assertEquals('Home', $breadcrumbs[0]['label']);
        $this->assertEquals('Admin', $breadcrumbs[1]['label']);
        $this->assertEquals('Users', $breadcrumbs[2]['label']);
        $this->assertTrue($breadcrumbs[2]['is_current']);
    }

    public function testPermissionFiltering(): void
    {
        $permissionManager = new PermissionManager();
        $menuManager = new MenuManager($this->testConfig, $permissionManager);

        // Test with authenticated user without admin role
        $user = new User(2, 'user', 'user@test.com', ['authenticated'], true);
        $menu = $menuManager->generateMenu('main_menu', $user);

        $this->assertCount(1, $menu); // Only home, admin should be filtered out
    }
}

Script di Performance Testing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?php
// tests/Performance/MenuPerformanceTest.php

class MenuPerformanceTest
{
    private MenuManager $menuManager;
    private CacheManager $cacheManager;

    public function __construct()
    {
        $this->setupLargeMenuConfig();
        $this->cacheManager = new CacheManager('/tmp/menu_cache_test');
        $this->menuManager = new MenuManager($this->largeConfig, null, $this->cacheManager);
    }

    public function testMenuGenerationPerformance(): void
    {
        $iterations = 100;

        echo "Testing menu generation performance...\n";

        // Without cache
        $startTime = microtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            $this->menuManager->generateMenu('main_menu');
        }
        $timeWithoutCache = microtime(true) - $startTime;

        // With cache
        $this->menuManager = new MenuManager($this->largeConfig, null, $this->cacheManager);

        $startTime = microtime(true);
        for ($i = 0; $i < $iterations; $i++) {
            $this->menuManager->generateMenu('main_menu');
        }
        $timeWithCache = microtime(true) - $startTime;

        echo "Without cache: {$timeWithoutCache}s\n";
        echo "With cache: {$timeWithCache}s\n";
        echo "Performance improvement: " . round(($timeWithoutCache / $timeWithCache), 2) . "x\n";

        // Cleanup
        $this->cacheManager->clear();
    }

    private function setupLargeMenuConfig(): void
    {
        // Generate a large menu structure for performance testing
        $this->largeConfig = [
            'main_menu' => [],
            'settings' => [
                'cache_enabled' => true,
                'cache_duration' => 3600
            ]
        ];

        // Generate 50 top-level items with 10 children each
        for ($i = 1; $i <= 50; $i++) {
            $children = [];
            for ($j = 1; $j <= 10; $j++) {
                $children["child_{$i}_{$j}"] = [
                    'label' => "Child {$i}.{$j}",
                    'url' => "/section{$i}/item{$j}",
                    'permissions' => ['public']
                ];
            }

            $this->largeConfig['main_menu']["item_{$i}"] = [
                'label' => "Item {$i}",
                'url' => "/section{$i}",
                'permissions' => ['public'],
                'children' => $children
            ];
        }
    }
}

// Run performance test
$performanceTest = new MenuPerformanceTest();
$performanceTest->testMenuGenerationPerformance();

Esempio Completo di Utilizzo

Setup della Navigazione

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
<?php
// public/index.php - Esempio di utilizzo completo

require_once '../vendor/autoload.php';

// Configurazione
$navigationConfig = require '../src/Config/navigation.php';

// Initialize managers
$cacheManager = new CacheManager('../cache/navigation');
$permissionManager = new PermissionManager();
$menuManager = new MenuManager($navigationConfig, $permissionManager, $cacheManager);

// Template renderer
$renderer = new NavigationRenderer($menuManager, '../src/Templates');

// Simulate user authentication
session_start();
$user = null;

if (isset($_SESSION['user_id'])) {
    // In una app reale, caricheresti l'utente dal database
    $user = new User(
        $_SESSION['user_id'],
        $_SESSION['username'] ?? 'guest',
        $_SESSION['email'] ?? '',
        $_SESSION['roles'] ?? ['authenticated'],
        true
    );
}

// Set global template variables
$renderer->setGlobalVariable('site_name', 'Il Mio Sito Web');
$renderer->setGlobalVariable('current_year', date('Y'));

// Get current page
$currentPage = $_GET['page'] ?? 'home';

?>
<!DOCTYPE html>
<html lang="it">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PHP Navigation System</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
</head>
<body>
    <!-- Main Navigation -->
    <?php echo $renderer->renderMenu('main_menu', 'bootstrap-navbar', $user); ?>

    <!-- Main Content -->
    <div class="container-fluid">
        <div class="row">
            <!-- Sidebar (for authenticated users) -->
            <?php if ($user && $user->isAuthenticated()): ?>
                <div class="col-md-3 col-lg-2 px-0">
                    <?php echo $renderer->renderMenu('sidebar_menu', 'sidebar', $user); ?>
                </div>
                <div class="col-md-9 col-lg-10">
            <?php else: ?>
                <div class="col-12">
            <?php endif; ?>

                <!-- Breadcrumbs -->
                <?php echo $renderer->renderBreadcrumbs('main_menu', 'default', $user); ?>

                <!-- Page Content -->
                <main class="py-4">
                    <?php
                    switch ($currentPage) {
                        case 'home':
                            echo '<h1>Benvenuto</h1>';
                            echo '<p>Questo è un esempio di sistema di navigazione PHP avanzato.</p>';
                            break;
                        case 'admin':
                            if ($user && $user->hasRole('admin')) {
                                echo '<h1>Pannello Amministrazione</h1>';
                                echo '<p>Contenuto riservato agli amministratori.</p>';
                            } else {
                                echo '<h1>Accesso Negato</h1>';
                                echo '<p>Non hai i permessi per accedere a questa sezione.</p>';
                            }
                            break;
                        default:
                            echo '<h1>Pagina non trovata</h1>';
                    }
                    ?>
                </main>

            </div>
        </div>
    </div>

    <!-- Footer Navigation -->
    <footer class="bg-dark text-light py-4 mt-5">
        <div class="container">
            <div class="row">
                <div class="col-md-6">
                    <?php echo $renderer->renderMenu('footer_menu', 'simple-list', $user); ?>
                </div>
                <div class="col-md-6 text-end">
                    <p>&copy; <?php echo date('Y'); ?> Il Mio Sito. Tutti i diritti riservati.</p>
                </div>
            </div>
        </div>
    </footer>

    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

Best Practices e Considerazioni Finali

Sicurezza

Sicurezza dei Menu Dinamici
  • Validazione rigorosa di tutti gli input utente
  • Escape HTML per prevenire XSS in etichette e URL
  • Controllo accessi sempre server-side, mai solo client-side
  • Sanitizzazione URL per prevenire redirect malicious
  • Rate limiting per richieste di generazione menu intensive

Performance

  1. Caching Intelligente

    • Cache dei menu per utenti con ruoli simili
    • Invalidazione cache mirata quando cambiano configurazioni
    • Cache distribuite per applicazioni multi-server
  2. Ottimizzazione Database

    • Query ottimizzate per caricamento permessi utente
    • Indici appropriati su tabelle utenti e ruoli
    • Connection pooling per applicazioni high-traffic
  3. Frontend Optimization

    • Lazy loading per menu con molti elementi
    • CSS e JavaScript minificati
    • CDN per asset statici

Accessibilità

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!-- Esempio di markup accessibile -->
<nav role="navigation" aria-label="Main navigation">
    <ul class="nav" role="menubar">
        <li role="none">
            <a href="/" role="menuitem" aria-current="page">Home</a>
        </li>
        <li role="none">
            <button role="menuitem" aria-expanded="false" aria-haspopup="true">
                Prodotti
            </button>
            <ul role="menu" aria-label="Submenu prodotti">
                <li role="none">
                    <a href="/products/a" role="menuitem">Categoria A</a>
                </li>
            </ul>
        </li>
    </ul>
</nav>

SEO Optimization

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// Schema.org markup per breadcrumbs
$schemaMarkup = [
    "@context" => "https://schema.org",
    "@type" => "BreadcrumbList",
    "itemListElement" => []
];

foreach ($breadcrumbs as $index => $crumb) {
    $schemaMarkup["itemListElement"][] = [
        "@type" => "ListItem",
        "position" => $index + 1,
        "name" => $crumb['label'],
        "item" => $crumb['url']
    ];
}

Monitoring e Debugging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class MenuDebugger
{
    public static function logMenuGeneration(string $menuType, ?User $user, float $executionTime): void
    {
        $logData = [
            'timestamp' => date('Y-m-d H:i:s'),
            'menu_type' => $menuType,
            'user_id' => $user?->getId(),
            'user_roles' => $user?->getRoles() ?? [],
            'execution_time_ms' => round($executionTime * 1000, 2),
            'memory_usage' => memory_get_usage(true)
        ];

        error_log('MENU_DEBUG: ' . json_encode($logData));
    }
}

Conclusioni e Prossimi Passi

Recap delle Competenze Acquisite

Sistema di Navigazione Enterprise-Grade Completato
  • Architettura modulare scalabile per progetti di qualsiasi dimensione
  • Sistema di permessi granulare con gerarchia ruoli
  • Template engine flessibile con cache avanzato
  • Menu multi-livello responsive con Bootstrap 5
  • Breadcrumbs SEO ottimizzati con Schema.org markup
  • Performance optimization con caching intelligente
  • Testing completo con unit test e performance benchmark
  • Sicurezza integrata con validation e sanitization

Roadmap di Estensioni Future

Il sistema sviluppato fornisce una base solida per implementazioni ancora più avanzate:

Funzionalità Enterprise

  • Menu dinamici da database con interfaccia di gestione
  • A/B testing per diverse configurazioni menu
  • Personalizzazione utente con menu customizzabili
  • Analytics integrato per tracking uso navigazione
  • Multi-tenant support per applicazioni SaaS

Integrazioni Moderne

  • REST API per menu management remoto
  • GraphQL endpoint per query menu ottimizzate
  • Architettura a microservizi per scalabilità enterprise
  • Event-driven updates con message queues
  • Machine learning per menu personalizzati automaticamente

Supporto Framework

  • Laravel package con Artisan commands
  • Symfony bundle con dependency injection
  • WordPress plugin per CMS integration
  • React/Vue.js components per SPA integration

Considerazioni Architetturali

Questo sistema di navigazione PHP dimostra come sia possibile creare soluzioni enterprise anche con tecnologie tradizionali come PHP nativo. Le competenze acquisite sono trasferibili a:

  • Framework moderni (Laravel, Symfony, CodeIgniter)
  • CMS enterprise (Drupal, WordPress multisite)
  • E-commerce platforms (Magento, WooCommerce)
  • Applicazioni custom con requisiti di scalabilità

La chiave del successo è stata l’applicazione di principi solidi di progettazione software: separazione delle responsabilità, caching intelligente, sicurezza by-design e testing automatizzato.


Padroneggia questi pattern di navigazione dinamica e diventa un esperto nello sviluppo di interfacce web professionali e scalabili!