Laravel 代码简洁之道(Clean Code)

作为生活的一部分,有时候你必须把恶心的事情搞定。编写整洁的代码将帮助您做到这一点!每个人都有不同的风格,代码可以随着时间的推移而改进。

FormRequests

FormRequests 从 Laravel 5.0 开始就存在了。这个我不知道,你可能也不知道。

传统验证

// UserController.php
class UserController extends Controller {  
    public function create() {
        $validator = Validator::make($request->all(), [
          'name'  => 'required',
          'email' => 'unique:users,email',
        ]);    
    if($validator->fails()) {
      return response()->json(['success' => false])
    }

    // ...
  }
}

让代码不言自明。 每次验证时,您的控制器都会变得臃肿。 通过依赖注入编写可扩展的代码。

$ php artisan make:request CreateUserPost

class CreateUserPost extends FormRequest {  
    public $rules = [
        'name'  => 'required',
        'email' => ['unique:users','email']
      ];  
      public function authorize() 
      {
          return true;
      }

      public function rules() 
      {
          return $this->rules;
      }
}
// UserController.php 现在变成了

class UserController extends Controller { 
    public function create(CreateUserPost $request) 
      {
        // 如果失败将会抛出 ValidationException   
        $validatedData = $request->validated();    
        // ... 
      }
}

服务层抽象

与在 Controller 中包含所有业务逻辑不同,添加服务层可以提高应用程序的可读性、可重用性和整体结构。

开始

创建一个新的服务将节省您的时间。

// InboxService.php
class InboxService {  
    public function getMessages(User $user): Mail
    {
        $messages = (new Mail())
                    ->where('to', $user->id)
                    ->orderBy('created_at', 'DESC')
                    ->get();

        return $messages;
   }
}

这是一个简单的示例,Controller 已经检查用户查看邮箱的权限(策略)。

现在,服务层负责任何不属于控制器的复杂逻辑,比如获取数据、变异数据、与内部 api 交互等。

注意: 有多种编写模型查询的方法。

  • Eloquent: 提供更简单的查询和关联
    Model::where(‘id’, $id)->first();
    (new Model())->where(‘id’, $id)->first();
  • DB: 对于大型数据库 DB::table(‘model’)->where(‘id’, $id)->first();
  • Mix: 对于方法链接和包开发有用$query = Model::query();
// MessageSearchService.php
class MessageSearchService 
{    
    private $query;   

    public function __construct() 
    {
        $this->query = Message::query();
    }    

    public function getMessages(User $user): Builder
    {
        return $this->query->where(function ($q) {
            $q->where('user_id', '!=', $user->id);
        });
    }    

    public function getFavorited(bool $favorited = true): Builder
    {
       return $this->getMessages()->where(function ($q) {
            return $q->where('is_favorited', favorited);
       });
    }
}

// 例如在一个 Controller 使用
// 将业务逻辑加载到服务层 $searchService;
public function __construct(MessageSearchService $searchService)
{
    $this->searchService = $searchService;
}

public function getAllMessages(): Collection
{
    return $this->searchService->getMessages(auth()->user())->get();
}

public function getFavoriteMessages(): Collection 
{
    return $this->searchService->getFavorited()->get();
}

模型范围和查询构造器

模型范围允许您封装和干掉代码,特别是处理数据库。 通过多个服务进行的大量高级查询可能会使您的代码变得臃肿。 您可以快速使代码易于阅读、测试和重用。

// SuperYacht.php
class SuperYacht extends Model
{
    /**
     * 将查询范围限定为仅获取未读消息。
     */
    public function scopeType($query, string $type = 'Express')
    {
        return $query->where('type', $type);
    }

    /**
     * 将查询范围限定为仅包括活动用户。
     * @param array $range Range in meters
     */
    public function scopeLength($query, array $range = [50, 200])
    {
        // 无需 if/else 即可轻松返回查询构建器
        return empty($range) 
               ? $query 
               : $query->whereBetween('length', $range);
    }
}

// SuperYachtController.php
class SuperYachtController extends Controller
{
    private $defaultRange = [25, 100];    

    public function show(int $id): ?SuperYacht
    {
        return SuperYacht::where('id', $id)
                           ->type('Cruiser')
                           ->length($this->defaultRange)
                           ->first();
    }
}

原始解决方案的问题

在应用我们的动态和灵活查询范围后,Superyacht正在从代码的重量中下沉。

class SuperYacht extends Model
{
    protected $fillable = ['name', 'type', 'length_m', 'length_ft'];        
    protected $appends = ['is_available'];    

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

    public function rooms() 
    {
        return $this->hasMany(Room::class);
    }    

    public function scopeType($query, string $type = 'Express')
    {
        return $query->where('type', $type);
    }
    ...
}

第二种解决方案

将查询范围移动到它自己的 Trait。 当您不需要复杂的服务层时,特征允许您跨模型、中间件、服务等重用代码。

// SearchableTrait.php
trait SearchableTrait
{
    public function scopeType($query, string $type = 'Express')
    {
        return $query->where('type', $type);
    }
}

// SuperYacht.php
class SuperYacht extends Model
{
    use SearchableTrait;    
    protected $fillable = [...];
    protected $appends = [...];
    public function (){}
}

查询生成器命令

相比其他内容,这部分比较高级, 可能需要一些时间来阅读和消化它。

AppServiceProvider.php -> boot() 方法中添加以下内容

/**
 * Usage: Model::whereLike('name', ['mike', 'joe'])->get();
 * Relationships: Model::whereLike('message.type', ['text', 'sms'])->get();
 */
Builder::macro('whereLike', function ($attributes, $terms) {
    $this->where(function ($query) use ($attributes, $terms) {
        foreach (array_wrap($attributes) as $attribute) {
            // 如果数组只有一个元素的话, 把元素的值封装在数组里 例如: $term = [$term];
            foreach (array_wrap($terms) as $term) {
                // 当 whereLike 包含一个关联值时,搜索这个关联值
                $query->when(str_contains($attribute, '.'),
                    function ($query) use ($attribute, $term) {
                        [$relationName, $relationAttribute] =       explode('.', $attribute);
                        // 验证当前查询中是否存在这个关联
                        $query->orWhereHas($relationName, function ($query) use ($relationAttribute, $term) {
                            $query->where($relationAttribute, 'LIKE', "%{$term}%");
                        });
                    },
                    //当字符串中不包含关联对象时进行的回退
                    function ($query) use ($attribute, $term) {
                        $query->orWhere($attribute, 'LIKE', "%{$term}%");
                    }
                );
            }
        }
    });
    // 最后返回 $query变量, 所以你可以调用其中的一些方法,比如  ->get(), ->first(), ->where() 等等
    return $this;
});
}

从模型调用生成器 Macro

// 常规查询
$message = Message::whereLike('subject', ['Dear', 'Hello!'])->get();// 关联查询
$message = Message::whereLike('type.name', ['text', 'sms'])->get();

结束笔记——编写代码文档可能会让人喘不过气——花时间充分掌握和应用你的知识将使你成为一个更好的开关人员。

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://medium.com/swlh/laravel-cleaner-...

译文地址:https://learnku.com/laravel/t/62621

本文为协同翻译文章,如您发现瑕疵请点击「改进」按钮提交优化建议
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
讨论数量: 1

复制过来的文章,代码排版乱了,麻烦改下

2年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!