控制器
新建控制器文件 app/controller/FooController.php
。
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function index(Request $request)
{
return response('hello index');
}
public function hello(Request $request)
{
return response('hello webman');
}
}
当访问 http://127.0.0.1:8787/foo
时,页面返回 hello index
。
当访问 http://127.0.0.1:8787/foo/hello
时,页面返回 hello webman
。
当然你可以通过路由配置来更改路由规则,参见路由。
提示
如果出现404无法访问,请打开config/app.php
,将controller_suffix
设置为Controller
,并重启。
控制器后缀
webman从1.3版本开始,支持在config/app.php
设置控制器后缀,如果config/app.php
里controller_suffix
设置为空''
,则控制器类似如下
app\controller\Foo.php
。
<?php
namespace app\controller;
use support\Request;
class Foo
{
public function index(Request $request)
{
return response('hello index');
}
public function hello(Request $request)
{
return response('hello webman');
}
}
强烈建议将控制器后缀设置为Controller
,这样能能避免控制器与模型类名冲突,同时增加安全性。
说明
- 框架会自动向控制器传递
support\Request
对象,通过它可以获取用户输入数据(get post header cookie等数据),参见请求 - 控制器里可以返回数字、字符串或者
support\Response
对象,但是不能返回其它类型的数据。 support\Response
对象可以通过response()
json()
xml()
jsonp()
redirect()
等助手函数创建。
控制器参数绑定
注意
此特性需要 webman-framework >= 1.6.0
例子
webman支持通过控制器方法参数自动绑定请求参数,例如
<?php
namespace app\controller;
use support\Response;
class UserController
{
public function create(string $name, int $age): Response
{
return json(['name' => $name, 'age' => $age]);
}
}
你可以通过GET
POST
方式传递name
和age
的值,也可以通过路由参数传递name
和age
参数,例如
Route::any('/user/{name}/{age}', [app\controller\UserController::class, 'create']);
优先级为路由参数
> GET
> POST
参数
默认值
假设我们访问 /user/create?name=tom
,我们将得到如下的错误
Missing input parameter age
原因是我们没有传递age
参数,可以通过给参数设置默认值来解决这个问题,例如
<?php
namespace app\controller;
use support\Response;
class UserController
{
public function create(string $name, int $age = 18): Response
{
return json(['name' => $name, 'age' => $age]);
}
}
参数类型
当我们访问 /user/create?name=tom&age=not_int
,我们将得到如下的错误
提示
这里为了方便测试,我们直接在浏览器地址栏输入参数,实际开发中应该通过POST
方式传递参数
Input age must be of type int, string given
这是因为接受的数据会按照类型进行转换,如果无法转换则会抛出support\exception\InputTypeException
异常,
因为传递的age
参数无法转换为int
类型,所以得到如上错误。
自定义错误
我们可以利用多语言自定义Missing input parameter age
和 Input age must be of type int, string given
这样的错误,
参考如下命令
composer require symfony/translation
mkdir resource/translations/zh_CN/ -p
echo "<?php
return [
'Input :parameter must be of type :exceptType, :actualType given' => '输入参数 :parameter 必须是 :exceptType 类型,传递的类型是 :actualType',
'Missing input parameter :parameter' => '缺少输入参数 :parameter',
];" > resource/translations/zh_CN/messages.php
php start.php restart
其它类型
webman支持的参数类型有int
float
string
bool
array
object
类实例
等,例如
<?php
namespace app\controller;
use support\Response;
class UserController
{
public function create(string $name, int $age, float $balance, bool $vip, array $extension): Response
{
return json([
'name' => $name,
'age' => $age,
'balance' => $balance,
'vip' => $vip,
'extension' => $extension,
]);
}
}
当我们访问 /user/create?name=tom&age=18&balance=100.5&vip=true&extension[foo]=bar
,我们将得到如下的结果
{
"name": "tom",
"age": 18,
"balance": 100.5,
"vip": true,
"extension": {
"foo": "bar"
}
}
类实例
webman支持通过参数类型提示传递类实例,例如
app\service\Blog.php
<?php
namespace app\service;
class Blog
{
private $title;
private $content;
public function __construct(string $title, string $content)
{
$this->title = $title;
$this->content = $content;
}
public function get()
{
return [
'title' => $this->title,
'content' => $this->content,
];
}
}
app\controller\BlogController.php
<?php
namespace app\controller;
use app\service\Blog;
use support\Response;
class BlogController
{
public function create(Blog $blog): Response
{
return json($blog->get());
}
}
当访问 /blog/create?blog[title]=hello&blog[content]=world
,我们将得到如下的结果
{
"title": "hello",
"content": "world"
}
模型实例
app\model\User.php
<?php
namespace app\model;
use support\Model;
class User extends Model
{
protected $connection = 'mysql';
protected $table = 'user';
protected $primaryKey = 'id';
public $timestamps = false;
// 这里需要添加可填充字段,防止前端传入不安全字段
protected $fillable = ['name', 'age'];
}
app\controller\UserController.php
<?php
namespace app\controller;
use app\model\User;
class UserController
{
public function create(User $user): int
{
$user->save();
return $user->id;
}
}
当访问 /user/create?user[name]=tom&user[age]=18
,我们将得到类似如下的结果
1
控制器生命周期
当config/app.php
里controller_reuse
为false
时,每个请求都会初始化一次对应的控制器实例,请求结束后控制器实例销毁,这与传统框架运行机制相同。
当config/app.php
里controller_reuse
为true
时,所有请求将复用控制器实例,也就是控制器实例一旦创建便常驻内存,所有请求复用。
注意
关闭控制器复用需要webman>=1.4.0,也即是说在1.4.0之前控制器默认是所有请求复用的,无法更改。注意
开启控制器复用时,请求不应该更改控制器的任何属性,因为这些更改将影响后续请求,例如
<?php
namespace app\controller;
use support\Request;
class FooController
{
protected $model;
public function update(Request $request, $id)
{
$model = $this->getModel($id);
$model->update();
return response('ok');
}
public function delete(Request $request, $id)
{
$model = $this->getModel($id);
$model->delete();
return response('ok');
}
protected function getModel($id)
{
// 该方法将在第一次请求 update?id=1 后会保留下 model
// 如果再次请求 delete?id=2 时,会删除 1 的数据
if (!$this->model) {
$this->model = Model::find($id);
}
return $this->model;
}
}
提示
在控制器__construct()
构造函数中return数据不会有任何效果,例如
<?php
namespace app\controller;
use support\Request;
class FooController
{
public function __construct()
{
// 构造函数中return数据没有任何效果,浏览器不会收到此响应
return response('hello');
}
}
控制器不复用与复用区别
区别如下
不复用控制器
每个请求都会重新new一个新的控制器实例,请求结束后释放该实例,并回收内存。不复用控制器和传统框架一样,符合大部分开发者习惯。由于控制器反复的创建销毁,所以性能会比复用控制器略差(helloworld压测性能差10%左右,带业务可以基本忽略)
复用控制器
复用的话一个进程只new一次控制器,请求结束后不释放这个控制器实例,当前进程的后续请求会复用这个实例。复用控制器性能更好,但是不符合大部分开发者习惯。
以下情况不能使用控制器复用
当请求会改变控制器的属性时,不能开启控制器复用,因为这些属性的更改会影响后续请求。
有些开发者喜欢在控制器构造函数__construct()
里针对每个请求做一些初始化,这时候就不能复用控制器,因为当前进程构造函数只会调用一次,并不是每个请求都会调用。