如何在Laravel写清洁控制器
#初学者 #php #laravel #cleancode

作为初学者Laravel开发人员 - 编写清洁控制器至关重要。根据我的经验,我遇到了许多Laravel项目,其中控制器长1000行。不必这样。

Bad Contollers written by junior devs

编写可维护和高效的控制器并不难。您只需要知道如何组织代码库即可。在本文中,我们将探讨基本技巧,以帮助您编写清洁器控制器。

清洁器Laravel控制器的3个提示:

  1. 具有单个责任的较小方法

  2. 用表单请求类验证

  3. 雄辩的API资源

我们将使用电子商务产品管理系统的简单示例来解释每个点。因此,让我们潜入!

1-单一责任的较小方法

控制器方法应具有一项责任。处理特定的HTTP请求并返回适当的响应。

您应该避免将复杂的逻辑添加到控制器方法中。将应用程序的请求处理部分从复杂的业务逻辑中解脱出来。您的控制器应仅负责“控制”执行流程。

让我们以一个示例理解这一点:

class ProductController extends Controller
{
    // ...

    public function update(UpdateProductRequest $request, $id)
    {
        $product = Product::findOrFail($id);

        $data = $request->validated();

        $product = Product::findOrFail($id);

        if ($request->hasFile('image')) {
            $image = $request->file('image');
            $imagePath = 'uploads/products/' . $image->hashName();
            $image->storeAs('public', $imagePath);
            $product->image = $imagePath;
        }

        $product->name = $request->input('name');
        $product->price = $request->input('price');
        $product->category_id = $request->input('category_id');
        $product->description = $request->input('description');
        $product->save();

        return redirect()->route('products.index')->with('success', 'Product updated successfully.');
    }

    // ...
}

这里的update方法是负责多件事。它还处理文件上传和存储图像,这不是其主要关注点。它应该只关注“更新”产品数据。

让我们看看如何清理update方法

class ProductController extends Controller
{
    private $productService;

    public function __construct(ProductService $productService)
    {
        $this->productService = $productService;
    }

    // ...

    public function update(UpdateProductRequest $request, $id): RedirectResponse
    {
        $product = Product::findOrFail($id);

        $data = $request->validated();

        if ($request->hasFile('image')) {
            $image = $request->file('image');
            $data['image'] = $this->productService->storeProductImage($image);
        }

        $this->productService->updateProduct($product, $data);

        return redirect()->route('products.index')->with('success', 'Product updated successfully.');
    }
}

请注意,我们仍然必须更新Product模型并上传图像。但是我们在处理这些特定操作的单独类ProductService中这样做。

创建服务类或操作以处理复杂的业务逻辑

这是一个非常简单的示例,但是您可以想象一个更复杂的功能,该功能可能涉及基于请求和模型的5种不同操作。它可能很快变得非常复杂。

关键要点:

  • 坚持每个控制器动作的单一责任

  • 保持您的方法集中和简洁,执行特定任务

  • 将复杂操作分解为较小的可重复使用方法

2-表单请求类验证

验证传入请求数据对于确保数据完整性和安全性至关重要。但是,如果您不小心 - 验证逻辑可能会使代码变得不可读。

您可以创建自定义表单请求类,以保持控制器清洁。这将验证逻辑与控制器本身分开。 Laravel提供了一种克服此类课程的好方法。

让我们探索这个。首先,考虑ProductControllerstore方法。这是您在控制器操作中实现的典型验证逻辑。

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Models\Product;

class ProductController extends Controller
{
    public function store(Request $request)
    {
        // Validate the request data
        $validatedData = $request->validate([
            'name' => 'required|string|max:255',
            'description' => 'required|string',
            'price' => 'required|numeric|min:0',
            'quantity' => 'required|integer|min:0',
            'category_id' => 'required|exists:categories,id',
            'brand_id' => 'required|exists:brands,id',
            'color' => 'required|string|max:50',
            'size' => 'required|string|max:20',
            'weight' => 'required|numeric|min:0',
            'images' => 'required|array',
            'images.*' => 'required|image|max:2048',
        ]);

        // Create a new product ...        
    }
}

这样做验证的缺点:

  1. 代码膨胀并降低可读性

  2. 跨控制器操作的代码重复

  3. 测试挑战并降低可检验性

因此,让我们看看如何使用Request类重写,并使其更清洁,可重复使用,更可测试

使用工匠命令创建请求类:

php artisan make:request CreateProductRequest

这将在app/Http/Requests目录中生成一个新的CreateProductRequest类。打开生成的文件并使用验证规则更新其rules方法:

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class CreateProductRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name' => 'required|string|max:255',
            'description' => 'required|string',
            'price' => 'required|numeric|min:0',
            'quantity' => 'required|integer|min:0',
            'category_id' => 'required|exists:categories,id',
            'brand_id' => 'required|exists:brands,id',
            'color' => 'required|string|max:50',
            'size' => 'required|string|max:20',
            'weight' => 'required|numeric|min:0',
            'images' => 'required|array',
            'images.*' => 'required|image|max:2048',
        ];
    }
}

现在,在您的ProductController中,您可以使用新的请求类,这样:

use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
use App\Http\Requests\CreateProductRequest;
use App\Models\Product;

class ProductController extends Controller
{
    public function store(CreateProductRequest $request)
    {
        // Retrieve the validated data
        $validatedData = $request->validated();

        // Create a new product ...
    }
}

如果可能的话,最好将请求类重复使用请求类。但是,如果您愿意,也可以为update操作创建不同的请求类。您可以根据需要创建尽可能多的自定义请求类。

关键要点:

  • 使用自定义表单请求与控制器单独的验证逻辑。

  • 在其他控制器/方法中重复使用请求类

3-雄辩的API资源

当您为API编写控制器时,格式化JSON响应非常常见。

如果您需要的响应格式与模式不同,则格式化逻辑也可以使您的控制器肿,并且很难阅读。有时您可能需要在多个API端点中重复使用相同的响应格式。

这是您可以利用雄辩的API资源来保持响应一致的地方,使您的格式可重复使用,并且控制器清洁

让我们看一个在控制器方法中格式化的API响应的示例:

use Illuminate\Http\Request;
use Illuminate\Http\Response;

class ProductController extends Controller
{
    public function index(Request $request)
    {
        $products = Product::with('brand', 'categories', 'features')->get();

        // Transform the product list with required attributes
        $transformedProducts = $products->map(function ($product) {
            return [
                'id' => $this->id,
                'name' => $this->name,
                'price' => $this->price,
                'description' => $this->description,
                'brand_name' => $product->brand->name,
                'brand_image' => $product->brand->image,
                'categories' => $product->categories->map(function ($category) {
                    return [
                        'name' => $category->name,
                        'alias' => $category->alias,
                        'image' => $category->image,
                    ];
                }),
                'features' => $product->features->map(function ($feature) {
                    return [
                        'title' => $feature->title,
                        'description' => $feature->description
                    ];
                })
            ];
        });

        return response()->json(['products' => $transformedProducts], Response::HTTP_OK);
    }
}

现在让我们看一下如何提取此逻辑并将其放入Resource

首先,创建ProductResource

php artisan make:resource Product

让我们将逻辑移至此类

use Illuminate\Http\Resources\Json\JsonResource;

class ProductResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'id' => $this->id,
            'name' => $this->name,
            'price' => $this->price,
            'description' => $this->description,
            'brand_name' => $this->brand->name,
            'brand_image' => $this->brand->image,
            'categories' => $this->categories->map(function ($category) {
                return [
                    'name' => $category->name,
                    'alias' => $category->alias,
                    'image' => $category->image,
                ];
            }),
            'features' => $this->features->map(function ($feature) {
                return [
                    'title' => $feature->title,
                    'description' => $feature->description
                ];
            })
        ];
    }
}

现在我们可以在控制器中使用它

use Illuminate\Http\Request;
use Illuminate\Http\Response;
use App\Http\Resources\ProductResource;

class ProductController extends Controller
{
    public function index(Request $request)
    {
        $products = Product::with('brand', 'categories', 'features')->get();

        $productResource = ProductResource::collection($products);

        return response()->json(['products' => $productResource], Response::HTTP_OK);
    }
}

您可以看到这种方法,与原始的index方法相比,我们的控制器要干净得多。它也可以重复使用。假设您有一个客户订单端点,希望在其中提供产品以及订单对象(非常常见的API要求),您可以重复使用相同的资源/集合

关键要点:

  • 使用雄辩的API资源使控制器更可读

  • 通过将数据转换逻辑与控制器分开

  • ,提高代码的可重复性和可维护性

在Laravel中编写清洁控制器对于构建可维护和高效的应用至关重要。养成编写清洁代码的习惯,而作为开发人员的生活将变得更加容易。与所有良好的习惯一样,它将需要进行故意的思考和实践。

通过练习这些原则并将其纳入您的开发工作流程中,您将在Laravel编写清洁剂和更有条理的控制器的方式中。

我还发表了一个关于getting started with background jobs in laravel的博客

我希望您能发现这很有价值 - 如果与他们相关的话,很棒的ð与您的人们分享。如果您有任何建议/评论,请自由。

快乐编码!