> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# 控制器

> 介绍 Laravel 中如何创建控制器以及如何与路由配合。

## 什么是控制器

除了将所有请求处理逻辑作为闭包定义在路由文件中，你还可以使用「控制器」类来组织这些行为。
控制器可以把相关的请求处理逻辑集中到一个类里。

例如，`UserController` 类可以处理与用户相关的所有请求，包括展示、创建、更新与删除用户等。
默认情况下，控制器存放在 `app/Http/Controllers` 目录下。

## 创建控制器

要快速生成新的控制器，请使用 `make:controller` Artisan 命令。
默认情况下，应用中所有的控制器都会存放在 `app/Http/Controllers` 目录下。

```shell theme={null}
php artisan make:controller UserController
```

## 基础控制器

控制器类可以拥有若干响应 HTTP 请求的 public 方法。

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Models\User;
use Illuminate\View\View;

class UserController extends Controller
{
    /**
     * 显示指定用户的个人资料
     */
    public function show(string $id): View
    {
        return view('user.profile', [
            'user' => User::findOrFail($id)
        ]);
    }
}
```

创建好控制器类与方法后，可以像下面这样定义指向该方法的路由。

```php theme={null}
use App\Http\Controllers\UserController;

Route::get('/user/{id}', [UserController::class, 'show']);
```

当接收到与指定路由 URI 匹配的请求时，`App\Http\Controllers\UserController` 类的 `show` 方法会被调用，并将路由参数传给该方法。

<Info>
  控制器不必继承任何基类。不过，若所有控制器都需要共享的方法，你可以让它们继承一个基础控制器类，这有时会很有用。
</Info>

## 单动作控制器

如果控制器动作特别复杂，将整个控制器类专门用于这一个动作会更方便。
只需在控制器中定义单一的 `__invoke` 方法。

```php theme={null}
<?php

namespace App\Http\Controllers;

class ProvisionServer extends Controller
{
    /**
     * 部署一台新的 Web 服务器
     */
    public function __invoke()
    {
        // ...
    }
}
```

在注册单动作控制器路由时无需指定控制器方法，只需将控制器名传给路由器。

```php theme={null}
use App\Http\Controllers\ProvisionServer;

Route::post('/server', ProvisionServer::class);
```

可以通过 `make:controller` Artisan 命令的 `--invokable` 选项生成可调用控制器。

```shell theme={null}
php artisan make:controller ProvisionServer --invokable
```

## 资源控制器

如果把应用中的每个 Eloquent 模型都看作「资源」，那么通常会对每种资源进行同一组操作（CRUD）。
Laravel 的资源路由允许你用一行代码将典型的增删改查（CRUD）路由分配给控制器。

使用 `make:controller` Artisan 命令的 `--resource` 选项，可以快速创建处理这些动作的控制器。

```shell theme={null}
php artisan make:controller PhotoController --resource
```

该命令会在 `app/Http/Controllers/PhotoController.php` 中生成控制器。
控制器会包含针对各类资源操作的方法桩。
接着，注册指向该控制器的资源路由。

```php theme={null}
use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class);
```

这一行路由声明会创建多个处理资源各种动作的路由。
生成的控制器已包含每个动作对应的方法桩。

| HTTP 方法   | URI                    | 动作      | 路由名            |
| --------- | ---------------------- | ------- | -------------- |
| GET       | `/photos`              | index   | photos.index   |
| GET       | `/photos/create`       | create  | photos.create  |
| POST      | `/photos`              | store   | photos.store   |
| GET       | `/photos/{photo}`      | show    | photos.show    |
| GET       | `/photos/{photo}/edit` | edit    | photos.edit    |
| PUT/PATCH | `/photos/{photo}`      | update  | photos.update  |
| DELETE    | `/photos/{photo}`      | destroy | photos.destroy |

<Info>
  运行 `php artisan route:list` 可以快速查看应用中所有路由的概览。
</Info>

### 部分资源路由

在声明资源路由时，可以指定控制器要处理的动作子集。

```php theme={null}
use App\Http\Controllers\PhotoController;

Route::resource('photos', PhotoController::class)->only([
    'index', 'show'
]);

Route::resource('photos', PhotoController::class)->except([
    'create', 'store', 'update', 'destroy'
]);
```

### API 资源路由

在为 API 声明资源路由时，通常会想排除 `create`、`edit` 这类展示 HTML 模板的路由。
使用 `apiResource` 方法可以自动排除这两个路由。

```php theme={null}
use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);
```

## 依赖注入

### 构造器注入

Laravel 的服务容器会用于解析所有 Laravel 控制器的依赖。
因此，你可以在控制器构造器中通过类型提示声明它所需的依赖。
声明的依赖会被自动解析并注入到控制器实例中。

```php theme={null}
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * 创建新的控制器实例
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
```

### 方法注入

除了构造器注入外，你也可以在控制器方法上通过类型提示声明依赖。
方法注入常见的用途是将 `Illuminate\Http\Request` 实例注入控制器方法。

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 保存新用户
     */
    public function store(Request $request): RedirectResponse
    {
        $name = $request->name;

        // 保存用户的处理...

        return redirect('/users');
    }
}
```

如果控制器方法同时从路由参数接收输入，请把路由参数放在其他依赖之后。
例如，路由定义如下：

```php theme={null}
use App\Http\Controllers\UserController;

Route::put('/user/{id}', [UserController::class, 'update']);
```

那么可以按如下方式定义控制器方法，既能类型提示 `Illuminate\Http\Request`，又能访问 `id` 参数。

```php theme={null}
<?php

namespace App\Http\Controllers;

use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;

class UserController extends Controller
{
    /**
     * 更新指定的用户
     */
    public function update(Request $request, string $id): RedirectResponse
    {
        // 更新用户的处理...

        return redirect('/users');
    }
}
```

<Tip>
  善用依赖注入可以更方便地在测试时替换为 mock，从而提升代码的可测试性。
</Tip>

## 下一步

<Card title="路由" icon="route" href="/zh/routing">
  回顾路由定义方式以及与控制器的联动。
</Card>


## Related topics

- [路由](/zh/routing.md)
- [URL 生成](/zh/urls.md)
- [校验](/zh/validation.md)
- [Laravel Wayfinder 介绍](/zh/blog/wayfinder-introduction.md)
- [HTTP 请求](/zh/requests.md)
