编程

如何审查 Laravel 代码

5 2026-04-29 01:18:00

优秀的代码就像一场精彩的对话……清晰、简洁,且易于理解。

在审查 Laravel 代码时,这一原则尤为适用。代码审查不仅仅是为了找出错误,更是为了确保项目在长期的发展过程中,始终保持安全、可扩展且易于维护。

尽管不同团队在具体偏好上可能存在差异,但在代码审查领域,确实存在一些行之有效的最佳实践,能够持续带来更优异的成果。在本文中,我们将深入探讨高效 Laravel 代码审查的关键要素。

那么,让我们开始吧。👇

1. 确保代码遵循最佳实践

Laravel 是一个拥有一套明确最佳实践的框架,代码审查应确保这些实践得到遵循。需要重点检查的关键实践包括:

  • 代码规范:请确保代码在编码风格与结构上符合 PSR-2 和 PSR-4 标准。统一的代码风格有助于新开发人员更快地融入团队。
    工具:
  • 命名规范:确保类、方法和变量均采用清晰且具有描述性的名称。例如,Eloquent 模型应遵循单数命名约定(如 User、Order),而控制器方法则应准确描述其执行的操作(如 store、update 等)。
  • 方法长度:避免编写过长的方法。如果方法承担了过多的职责,应将其拆分为更小、可复用的代码块。
  • 关注点分离:确保代码遵循单一职责原则(SRP)。控制器不应直接处理业务逻辑,这部分职责应由服务类来管理。

    示例:
// Bad: Too many responsibilities and unclear logic
public function createOrder(Request $request) {
    $user = User::find($request->user_id);
    $order = new Order();
    $order->user_id = $user->id;
    $order->total = $request->total;
    $order->save();
    Mail::to($user->email)->send(new OrderConfirmation($order));
}

在上述示例中,发送电子邮件的逻辑应提取至服务或事件中,以提升代码的清晰度并实现职责分离。

// Improved version
public function createOrder(CreateOrderRequest $request, OrderService $orderService) {
    $order = $orderService->create($request->validated());
    return response()->json(['order' => $order]);
}
  • 统一的缩进与格式:这包括在间距、对齐和缩进方面保持一致性。整洁的格式能显著提升代码的可读性。
  • 避免使用“魔术字符串”和“魔术数字”:切勿在整个代码库中散布使用硬编码的字符串或数字;应确保改用常量或配置文件来替代。
if ($user->role == 'admin') {  // 'admin' could be defined in a constant
    $discount = $total * 0.15;
}

改成:

class RoleConstants {
   const ROLE_ADMIN = 'admin';
}

if ($user->role === self::ROLE_ADMIN) {
    // ...
}
  • 清晰简练的注释:有用与冗余的注释之间仅有一线之隔。请确保仅在代码逻辑并非一目了然之处添加注释,并避免撰写不必要或显而易见的注释。
Example:
// This loop iterates through each user
foreach ($users as $user) {
    $this->sendNotification($user);
}

改成:

// Notify users about the new policy changes
foreach ($users as $user) {
    $this->sendNotification($user);
}

2. 使用设计模式

Laravel 的灵活性使其能够轻松采使用各类设计模式,从而提升应用的可维护性、可扩展性及可测试性。一个结构良好的 Laravel 应用往往依赖于这些设计模式来实现关注点分离,并保持代码库的整洁。

在进行代码审查时,应留意哪些地方已有效地运用了设计模式,以及在何处引入设计模式能够有助于降低代码的复杂度。

服务模式

当业务逻辑不适合直接置于控制器(Controller)或模型(Model)中时,应将其提取至服务类(Service 类)中。这样做既能让控制器专注于处理请求,又能将可复用的逻辑封装在专门的服务中。

代码审查时的关注点:

职责过多、显得过于臃肿的控制器或模型。

可提取至服务类中的重复逻辑。

// Controller interacting with the service layer
$order = $this->orderService->processOrder($request->all());
  • 工厂模式

工厂模式能够以一种整洁且可复用的方式创建对象,尤其适用于对象创建涉及复杂逻辑或依赖关系的情况。Laravel 的模型工厂非常适合用于数据填充(Seeding)和测试,但你也可以针对其他用例定义自定义工厂

代码审查时的关注点:

重复的或包含条件判断的对象创建代码。

可以将创建逻辑封装进工厂的机会。

  • 策略模式 
     

策略模式允许你针对特定任务定义多种可相互替换的算法,并能在运行时动态地在它们之间进行切换。这对于处理不同的支付方式、通知渠道或定价规则尤为实用。

代码审查时的关注点:

用于决定具体行为的冗长 if/elseswitch 语句。

针对流程相似但略有差异的场景,存在重复的逻辑代码。

  • 观察者模式
     

观察者模式能帮助你以一种整洁且解耦的方式,对模型事件(例如创建、更新或删除)做出响应。Laravel 内置的模型观察者以及事件/监听器机制,使得这一实现变得轻而易举。

代码审查时的关注点:

与模型生命周期事件相关的业务逻辑被直接置于控制器或模型内部。

存在可进行集中化管理的重复触发逻辑。

Order::observe(OrderObserver::class);
  • 装饰器模式
     

这种模式允许你在不修改对象核心代码的前提下,动态地为其添加行为;这对于实现缓存、日志记录或增强 API 响应等任务而言,是理想的解决方案。

代码审查时的关注点:

针对同一组方法重复添加了逻辑。

缓存或指标收集等功能实现得不一致。

$orderService = new CachingOrderService(new OrderService());

3. 事件 & 监听器

Laravel 凭借其“事件”与“监听器”机制,为事件驱动架构提供了强有力的支持。开发者不应将过多的逻辑堆砌在控制器或服务中,而应利用事件来实现应用程序各组件之间的解耦。在进行代码审查时,请重点关注以下方面:

针对耗时较长或需要在后台执行的任务,是否已通过触发事件的方式进行处理?例如,发送电子邮件、生成报表或触发通知等任务,都应当利用事件机制以异步方式来完成。

// Bad: Direct email sending in a controller
Mail::to($user->email)->send(new OrderConfirmation($order));

// Good: Dispatch job to queue
SendOrderConfirmation::dispatch($order);

监听器是否轻量且职责单一?请确保监听器不包含复杂的业务逻辑;如有必要,应将此类逻辑下移至服务类中处理。

// Fire an event
event(new OrderPlaced($order));
// Listener that handles background work
class SendOrderConfirmation implements ShouldQueue {
    public function handle(OrderPlaced $event) {
        Mail::to($event->order->user->email)->send(new OrderConfirmation($event->order));
    }
}

值得注意的是:

过度使用事件可能会让你的 Laravel 应用变得像一场“马戏团表演”般混乱。尽管事件有助于解耦业务逻辑并提升可维护性,但若滥用,反而会导致程序流程变得杂乱无章、难以追踪。开发者应当审慎把握这一平衡,以避免架构变得过度复杂。

3. 处处使用依赖注入(DI)

恰当使用依赖注入对于提升代码的可测试性和灵活性至关重要。在代码评审过程中,请确保:

控制器的依赖项是通过构造函数注入的,而非在方法内部硬编码。

// Good: Dependency injection in the constructor
public function __construct(UserService $userService) {
    $this->userService = $userService;
}
  • 服务和辅助函数在必要之处通过注入方式引入,而非在代码中直接使用 app() 或全局 Facade。这提升了代码的可测试性,并使类之间的依赖关系更加清晰。
// Avoid this
app(UserService::class)->getAll();

// Use DI instead
public function __construct(UserService $userService) {
    $this->userService = $userService;
}

 

5. 使用速率限制与节流

对于 API 或表单,请检查限流(Rate Limiting)与节流(Throttling)机制的运用,以防范滥用行为及拒绝服务攻击。Laravel 借助 throttle 中间件,能够轻松实现这些功能。

API 限流:请确保对 API 路由进行限流以限制滥用。

Route::middleware('throttle:60,1')->group(function () {
    Route::get('/user', 'UserController@show');
});

登录/注册限流: 检查用户登录或注册端点是否采用了限流机制,以防范暴力破解攻击。

6. 安全最佳实践

在任何 Laravel 应用中,安全性都至关重要。在进行代码审查时,请特别留意潜在的安全漏洞:

  • SQL 注入:确保所有数据库查询都采用参数化处理,或使用 Eloquent ORM 来避免直接执行原始 SQL 语句,从而防止 SQL 注入攻击。
  • 输入验证:确保所有接收到的数据在进行处理或存储之前都经过了严格验证。利用 Laravel 的请求验证规则来净化和校验输入数据,从而降低因接收到非预期数据而引发系统故障的风险。
  • 文件上传安全:检查上传的文件是否经过了严格验证,以确保其文件类型、大小和内容均符合预设规范。将上传的文件存储在 public 目录之外,以防止未经授权的直接访问;同时,对上传的文件进行重命名处理,避免恶意代码伪装成特定扩展名文件并被存储在系统中。
  • 密码存储与策略:请确认密码已使用安全的算法(例如 bcrypt 或 Argon2)进行哈希处理,而非以明文形式存储。此外,建议实施强密码策略,以进一步提升安全性。
  • 日志与监控:确保应用记录关键的安全事件(例如登录失败或权限拒绝),同时避免记录敏感信息。配置监控工具,以便在检测到可疑活动时发出警报。
  • 跨站脚本(XSS):审查代码,确保对用户输入进行了恰当的转义处理,特别是在将数据输出至视图(Views)时。在 Blade 视图中,请使用 {{ }} 对变量进行转义;除非绝对必要,否则应避免使用 {{!! !!}}
  • CSRF 防护:确保表单均启用了 Laravel 内置的 CSRF Token 防护机制。
  • 敏感数据泄露:确保没有任何敏感信息(例如密码、API 密钥或数据库凭据)被硬编码在代码中,或因疏忽而被意外暴露。

示例:

// Bad: Vulnerable to SQL Injection
DB::select("SELECT * FROM users WHERE email = '$email'");

改成使用 Eloquent 或者参数化查询:

// Good: Secure
User::where('email', $email)->first();

7. 性能考量

在进行代码审查时,请特别留意代码的执行效率。性能低下不仅会严重影响用户体验,还会阻碍系统的可扩展性。

  • 数据库查询:确保代码中不存在未经优化的查询语句。重点排查是否存在“N+1”问题——即在循环中触发了多次数据库查询。应充分利用 Laravel 的“预加载”(Eager Loading)机制,通过 with() 方法在单次查询中一次性加载所有关联模型。
  • 缓存机制:鼓励针对高频访问的数据使用缓存,以此减轻数据库的负载压力。
  • 数据库索引:审查数据库迁移(Migrations)文件,确保那些在查询中频繁涉及的字段已正确地建立了索引。
  • 大数据集分页:对于大型数据集,务必采用分页加载的方式,而非将其一次性全部载入内存。Laravel 提供了 paginate() 方法,能够高效地处理此类场景,从而最大限度地降低资源消耗并缩短数据加载时间。

示例:

// Bad: N+1 problem in a loop
$posts = Post::all();
foreach ($posts as $post) {
    echo $post->author->name;
}

// Good: Using eager loading to fix N+1
$posts = Post::with('author')->get();
foreach ($posts as $post) {
    echo $post->author->name;
}

8. 测试覆盖率与代码可靠性

测试是任何代码审查中至关重要的环节。虽然追求 100% 的测试覆盖率并不总是切合实际——甚至可能拖慢开发效率——但确保所有核心功能都能得到自动化测试的可靠覆盖却是必不可少的。我们的目标并非要覆盖每一行代码,而是要确保关键功能与工作流程能够免受意外问题的侵扰。

在部署新版本时,你自然希望确信:此次更新不会对应用造成任何破坏。一套稳健的测试套件能够助你立于不败之地——它能及早捕获潜在问题,从而有效避免生产环境下的系统停机。

以下是应当重点关注的方面:

单元测试:确保利用单元测试对核心逻辑及关键功能进行了全面覆盖。这类测试有助于保证各个独立组件都能按预期正常运行。

功能测试与集成测试:验证所有主要功能及集成模块均已接受了彻底的测试。对于捕捉应用不同模块之间可能存在的问题、并确保系统功能顺畅无阻而言,这类测试至关重要。

示例:

public function test_create_order() {
    $response = $this->post('/orders', [
        'user_id' => 1,
        'total' => 100
    ]);

    $response->assertStatus(201);
    $this->assertDatabaseHas('orders', [
        'user_id' => 1,
        'total' => 100,
    ]);
}
  • 测试中的模拟与依赖注入:在测试期间对依赖项进行模拟,以实现工作单元的隔离,从而确保测试不依赖于外部服务。
// Example of mocking a service in a test 
$mock = Mockery::mock(UserService::class); 
$mock->shouldReceive('kissUser')->once()->andReturn(collect([$user1])); 
$this->app->instance(UserService::class, $mock);
  • 测试中的数据库事务:在运行集成测试时,利用数据库事务来保持数据库的整洁。Laravel 会自动回滚测试期间对数据库所做的更改。
<?php

namespace Tests\Feature;

use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
use App\Models\User;

class ExampleTest extends TestCase
{
    // Use the RefreshDatabase trait to handle transactions
    use RefreshDatabase;

    public function test_example()
    {
        // Arrange: Set up any necessary data
        $user = User::factory()->create([
            'name' => 'John Doe',
        ]);

        // Act: Perform the action being tested
        $response = $this->actingAs($user)->get('/profile');

        // Assert: Verify the expected outcome
        $response->assertStatus(200);
        $this->assertDatabaseHas('users', [
            'name' => 'John Doe',
        ]);
    }
}

 

9. 重构机会

识别代码中可能需要重构的区域:

  • 重复代码:将重复的代码整合为可复用的组件、Trait 或辅助方法。
  • 臃肿的控制器:将业务逻辑从控制器中移至服务类、任务或事件中,以保持控制器精简。
  • 庞大的模型(Fat Models):将复杂的逻辑从模型层迁移至服务类或仓库(Repository)中。理想情况下,模型应专注于数据结构与关系,而非承载繁重的业务逻辑。
  • 非必要的依赖:移除 composer.json 文件中任何未使用的库或依赖项,以避免代码冗余和膨胀。
  • 嵌套循环:将嵌套循环简化或重构为独立的方法,或者考虑使用集合,以提升代码的可读性和性能。
  • .紧耦合代码:通过使用依赖注入,或采用工厂模式、仓储模式等设计模式,来降低类之间的耦合度。这使得代码更易于测试和维护。
  • 大型视图:将大型视图拆解为更小、可复用的组件。这有助于减少代码冗余,并提升前端代码的可维护性

 

10. 强制执行 SOLID 原则

在代码审查过程中,最具价值的检查项之一,便是验证代码是否遵循了 SOLID 原则。这五项原则是构建整洁、易于维护且可扩展的面向对象设计的基础。

它们能够确保,随着应用的不断壮大,你的 Laravel 代码库依然保持高度的灵活性,并易于进行功能扩展。

单一职责原则 (SRP)

每一个类、方法或组件都应当只有一个引起其变更的理由——这意味着它应当只承担一项单一且定义明确的职责。违反这一规则往往会导致类变得过于臃肿,试图“包揽一切”,从而使其难以维护和测试。

代码评审时应重点关注:

过于庞大的控制器(Controller)或模型(Model),混杂了多种类型的逻辑(例如:数据验证、业务规则、数据访问)。

服务类.执行非相关任务。

代码行数长达数百行,或负责处理多项流程的方法。

开/闭原则

软件实体(类、模块、函数)应当对扩展开放,但对修改封闭。你应该能够在不更改现有代码的前提下引入新功能——这有助于降低无意中破坏现有功能的风险。

代码评审时的关注点:

在添加新功能时,对核心类进行了反复修改。

.包含大量条件判断(如 ifswitch)的逻辑,而这些逻辑本可以通过多态或策略模式来替代。

里氏替换原则(LSP)
 

子类应当能够完全替换其父类,且不改变预期的行为。这确保了继承关系的逻辑合理性,并保证多态机制能够按预期正常工作。

代码评审时的关注点:

子类重写(Override)父类方法的方式破坏了父类所约定的契约。

被重写的方法存在不一致的返回值类型,或产生了意料之外的副作用。

接口隔离原则(ISP)

客户端不应被迫依赖它们不使用的接口。换言之,应保持接口小巧且职责单一。

代码评审时的关注点:

定义了不相关方法的庞大接口或抽象类。

包含大量空方法或未被使用方法的具体实现。

总结

在 Laravel 开发中,代码审查绝不仅仅是为了找出 Bug 或确保规则合规,其核心在于让每一位参与项目的成员都能从中受益,从而共同提升项目的整体质量。

请专注于保持代码的整洁、易读与安全。当下所做的每一处改进,都能为你日后省去诸多麻烦,并助力团队在未来实现更高效的迭代与发展。

因此,请怀揣对细节的敏锐洞察力,全情投入;继续打造那些让你与你的团队都能引以为豪的优秀应用吧!