HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/be.naniguide.com/app/Library/ModuleManager.php
<?php

namespace App\Library;

use App\Library\Facades\Hook;

/**
 * ModuleManager — typed extension surface for plugins that wrap or replace
 * parts of the customer-facing application (e.g. a "frontend skin" plugin).
 *
 * Methods are typed entry points; the Hook system underneath is an
 * implementation detail. Callers (LoginController, middleware, plugin
 * ServiceProvider) never reference Hook:: directly — swap to Laravel
 * events later without touching call sites.
 *
 * Growth policy: keep this class small. Add a typed method only when an
 * extension point is stable and used by more than one plugin. Experimental
 * surfaces stay on raw Hook:: until promoted here.
 */
final class ModuleManager
{
    private const H_ROUTE_DECISION = 'frontend-route-decision';

    // -------------------------------------------------------------------------
    // Registration API — plugins call from their ServiceProvider::boot()
    // -------------------------------------------------------------------------

    /**
     * Per-request gate for routes in the 'frontend' middleware group.
     *
     * @param  callable  $resolver  fn(\Illuminate\Http\Request $request): ?string
     *                              Return null to continue, or a URL string to
     *                              redirect the request there.
     *
     * Throws if a second plugin tries to claim this slot.
     */
    public static function setRouteResolver(callable $resolver): void
    {
        Hook::set(self::H_ROUTE_DECISION, $resolver);
    }

    /**
     * Declarative whitelist — list every route-name pattern (fnmatch globs)
     * the plugin wants to keep accessible inside the host's `frontend`
     * middleware group. Anything not matching aborts with HTTP 404.
     *
     * The plugin author edits ONE array as the source of truth — no inline
     * resolver closure, no separate redirect target to keep in sync. When
     * a new host route surfaces that customers must reach (e.g. a future
     * `refactor.account.email_verify`), add the pattern here.
     *
     * For non-trivial decisions (per-plan entitlements, custom redirect),
     * use {@see setRouteResolver()} directly instead.
     */
    public static function setAllowedRoutes(array $patterns): void
    {
        self::setRouteResolver(function ($request) use ($patterns) {
            $name = $request->route()?->getName() ?? '';
            foreach ($patterns as $p) {
                if ($p === $name || fnmatch($p, $name)) {
                    return null; // continue
                }
            }
            abort(404);
        });
    }

    // -------------------------------------------------------------------------
    // Resolution API — core calls at runtime
    //
    // resolveRouteDecision() calls Hook::setIfEmpty(...) immediately before
    // perform(), following the established pattern in SubscriberController
    // and Plugin.php. Boot order is irrelevant: if a plugin's set() ran
    // during boot, setIfEmpty() here no-ops; if no plugin registered, the
    // default gets installed lazily on first request.
    // -------------------------------------------------------------------------

    public static function resolveRouteDecision($request): ?string
    {
        Hook::setIfEmpty(self::H_ROUTE_DECISION, fn($r) => null);
        return Hook::perform(self::H_ROUTE_DECISION, [$request]);
    }
}