Laravel9 如何实现单控制器数据全局共享至所有视图
针对跨视图复用搜索结果、保留搜索输入的需求,不要使用视图变量共享、容器单例这类仅支持单次请求生命周期的方案,最合适的实现方式是基于Session做用户级状态存储,配合全局视图共享实现全页面复用,具体实现步骤如下:
一、先修复现有代码的基础问题
你当前代码有三个容易出问题的点:
- 表单路由调用错误:路由命名为
search,表单里写route('/search')会报路由不存在错误,应该改为route('search') - 直接返回视图不符合PRG规范:POST请求后直接返回视图,用户刷新页面会触发重复提交弹窗,应该用重定向返回目标页面
- 客户端ID是唯一标识,查询用
first()返回单条数据即可,不需要用get()返回集合
二、核心实现逻辑
Session是Laravel提供的用户级跨请求存储机制,数据和单个用户绑定,不会出现不同用户数据串扰的问题,完全匹配你的需求:
- 搜索提交后把客户端ID、查询结果存入Session,跨页面跳转不会丢失
- 重复搜索相同ID时直接复用Session内数据,无需重复查询数据库
- 全局把Session内的搜索数据共享给所有视图,不需要每个控制器单独传值
- 搜索框直接读取Session内存储的客户端ID做值回填,切换页面也能保留输入内容
三、具体代码实现
1. 改造搜索控制器
<?php namespace App\Http\Controllers; use Illuminate\Support\Facades\DB; use Illuminate\Http\Request; class SearchController extends Controller { public function get_kcli(Request $request) { $kcli = $request->input('kcli'); $targetRoute = $request->input('currentPage'); // 搜索值为空时清除已存储的搜索状态 if (empty($kcli)) { $request->session()->forget(['current_client_id', 'current_client_data']); return redirect()->route($targetRoute); } // Session中已存在相同ID的搜索结果,直接跳转不重复查库 if ((int)session('current_client_id') === (int)$kcli) { return redirect()->route($targetRoute); } // 查询客户端数据 $clientData = DB::connection('oracle') ->table('CLIENTS') ->where('KCLI', $kcli) ->first(); // 未查到数据返回错误提示 if (!$clientData) { return back()->withErrors(['kcli' => "未找到ID为{$kcli}的客户端"]); } // 搜索结果写入Session session([ 'current_client_id' => $kcli, 'current_client_data' => $clientData ]); return redirect()->route($targetRoute); } }
2. 新增全局中间件共享视图变量
不要直接在AppServiceProvider里做视图共享,服务提供者启动时Session还未初始化,会读不到数据,通过中间件共享更稳妥:
首先执行artisan命令生成中间件:php artisan make:middleware ShareSearchData
编辑app/Http/Middleware/ShareSearchData.php文件:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Illuminate\Support\Facades\View; class ShareSearchData { public function handle(Request $request, Closure $next) { // 把搜索数据共享给所有视图,所有模板可直接调用变量 View::share('currentClientId', session('current_client_id')); View::share('currentClient', session('current_client_data')); return $next($request); } }
打开app/Http/Kernel.php,把中间件注册到web中间件组,注意要放在StartSession中间件后面:
protected $middlewareGroups = [ 'web' => [ // 其他原有中间件... \Illuminate\Session\Middleware\StartSession::class, // 新增这行注册共享中间件 \App\Http\Middleware\ShareSearchData::class, // 其他原有中间件... ], ];
3. 改造搜索表单组件
修复路由错误,增加搜索值回填、错误提示,移除多余的@method('POST')声明(表单默认提交方式就是POST):
<nav class="navbar navbar-light"> <form method="POST" class="form-inline position-relative" action="{{ route('search') }}"> @csrf <input class="form-control shadow-none" name="kcli" id="kcli" type="number" placeholder="Codice..." aria-label="Search" value="{{ $currentClientId ?? '' }}" > <input type="text" name="currentPage" value="{{ Route::currentRouteName() }}" hidden> <button type="submit" class="btn btn-light search-btn"><i class="fas fa-search"></i></button> @error('kcli') <div class="text-danger small position-absolute" style="top:100%;left:0">{{ $message }}</div> @enderror </form> </nav>
四、使用方式
改造完成后,你在任意视图文件里都可以直接调用两个变量:
$currentClientId:当前搜索的客户端ID,搜索框回填就是用这个值$currentClient:对应客户端的全量数据,不需要每个控制器单独查询、传值,只要用户不修改搜索条件、不手动清空搜索,切换任意页面都能直接读取,不会重复查询数据库。
可选性能优化
如果单条客户端数据字段多、体积大,不想把全量数据存在Session里,可以只在Session里存客户端ID,把中间件的逻辑改成带全局缓存的查询,多个用户搜索同一个客户端ID可以共用缓存,进一步减少数据库查询:
// ShareSearchData中间件handle方法内逻辑替换 $clientId = session('current_client_id'); $currentClient = null; if ($clientId) { // 客户端数据缓存24小时,表数据更新时可以手动清除对应缓存 $currentClient = cache()->remember("client_info_{$clientId}", 86400, function() use ($clientId) { return DB::connection('oracle')->table('CLIENTS')->where('KCLI', $clientId)->first(); }); } View::share('currentClientId', $clientId); View::share('currentClient', $currentClient);
对应控制器里只需要存current_client_id到Session即可,不需要存全量数据。
内容的提问来源于stack exchange,提问作者user18182496

