在Laravel项目开发过程中,多个控制器往往存在相似的业务逻辑,比如公共的数据查询、权限校验、视图变量赋值等操作。如果直接在每个控制器中重复编写这些逻辑,不仅会增加代码量,还会提升后续维护的难度。通过控制器继承的方式,我们可以将公共逻辑抽离到父控制器中,子控制器继承父控制器后可以直接复用这些逻辑,同时还能根据自身的业务需求动态覆盖父控制器中定义的视图变量,实现差异化的页面展示。

一、基础父控制器定义
首先我们需要创建一个基础的父控制器,将需要复用的公共逻辑和默认的视图变量定义在这个控制器中。父控制器可以继承Laravel自带的AppHttpControllersController基类,这样就能保留框架原生的控制器功能。
下面是一个基础父控制器的示例代码:
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
class BaseController extends Controller
{
// 定义默认的视图变量数组
protected $viewData = [
'site_name' => '我的站点',
'footer_text' => '版权所有 2024',
'nav_items' => ['首页', '关于我们', '联系我们']
];
// 公共的初始化方法,用于赋值视图变量
protected function initViewData()
{
// 可以在这里添加公共的数据处理逻辑
$this->viewData['current_time'] = date('Y-m-d H:i:s');
}
// 公共的渲染视图方法
protected function renderView($viewName)
{
$this->initViewData();
return view($viewName, $this->viewData);
}
}
二、子控制器继承与逻辑复用
子控制器只需要继承我们定义的BaseController,就可以直接复用父控制器中的renderView方法、initViewData方法以及默认的viewData属性,不需要重复编写这些逻辑。
下面是一个子控制器的示例,它继承了BaseController并复用了父类的视图渲染逻辑:
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
class HomeController extends BaseController
{
// 子控制器可以定义自己的方法
public function index()
{
// 直接调用父类定义的渲染视图方法
return $this->renderView('home.index');
}
}
此时访问HomeController的index方法,页面会接收到父控制器中定义的site_name、footer_text、nav_items以及initViewData方法中添加的current_time这些视图变量。
三、动态覆盖视图变量
如果子控制器需要修改父控制器中定义的默认视图变量,只需要重新定义viewData属性,或者在调用渲染方法前修改viewData的值即可,这就是动态覆盖的过程。
3.1 重新定义viewData属性覆盖
子控制器可以直接重写viewData属性,这样父控制器的默认viewData会被完全替换:
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
class AboutController extends BaseController
{
// 重写viewData属性,覆盖父类的默认值
protected $viewData = [
'site_name' => '关于我们 - 我的站点',
'footer_text' => '关于我们页面专属底部文本',
'nav_items' => ['首页', '关于我们', '联系我们'],
'page_title' => '关于我们'
];
public function index()
{
return $this->renderView('about.index');
}
}
3.2 运行时动态修改viewData
如果只需要在某个具体的方法中临时修改视图变量,不需要重写整个viewData属性,可以在方法中直接修改$this->viewData的值:
<?php
namespace AppHttpControllers;
use IlluminateHttpRequest;
class ArticleController extends BaseController
{
public function detail($id)
{
// 模拟查询文章数据
$article = [
'id' => $id,
'title' => '测试文章标题',
'content' => '这是文章的具体内容'
];
// 动态修改视图变量,覆盖父类的默认值
$this->viewData['page_title'] = $article['title'];
$this->viewData['article'] = $article;
$this->viewData['site_name'] = $article['title'] . ' - 我的站点';
// 调用父类渲染方法
return $this->renderView('article.detail');
}
}
四、注意事项
- 父控制器中的属性和方法需要根据访问权限合理设置,如果是需要子类复用的逻辑,建议设置为
protected权限,避免外部直接访问。 - 如果父控制器的
initViewData方法中有子类不需要的逻辑,子类可以重写这个方法,先调用parent::initViewData()执行父类的逻辑,再添加自己的逻辑。 - 视图变量的覆盖是数组层面的替换,如果父类的
viewData有某个键,子类没有定义,那么子类的viewData中就不会包含这个键,除非在子类中显式定义。
五、适用场景
这种控制器继承加视图变量动态覆盖的模式,非常适合以下场景:
- 多个页面有公共的站点配置变量,比如站点名称、导航栏、底部信息。
- 多个控制器需要执行相同的权限校验、数据预处理逻辑。
- 不同页面需要基于同一套基础变量做差异化调整,不需要完全重新定义所有变量。
通过这种模式,我们可以大幅减少重复代码的编写,提升项目的可维护性,后续如果需要修改公共逻辑,只需要在父控制器中调整即可,所有子控制器都会自动生效。