Controller

Create a new controller file 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');
    }
}

When accessing http://127.0.0.1:8787/foo, the page returns hello index.

When accessing http://127.0.0.1:8787/foo/hello, the page returns hello webman.

Of course, you can change the routing rules through the route configuration, see Routing.

Tip
If a 404 error occurs and access is not available, please open config/app.php, set controller_suffix to Controller, and restart.

Controller Suffix

Starting from version 1.3, Webman supports setting the controller suffix in config/app.php. If controller_suffix in config/app.php is set to blank '', the controller will look like this:

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');
    }
}

It is strongly recommended to set the controller suffix to Controller, as this avoids conflicts between controller and model class names, while enhancing security.

Description

  • The framework automatically passes the support\Request object to the controller, which can be used to obtain user input data (like get, post, header, cookie, etc.), see Request.
  • The controller can return numbers, strings, or support\Response objects, but cannot return other types of data.
  • The support\Response object can be created using helper functions such as response(), json(), xml(), jsonp(), redirect(), etc.

Controller Parameter Binding

Example

Webman supports automatic binding of request parameters to controller method parameters. For example:

<?php
namespace app\controller;
use support\Response;

class UserController
{
    public function create(string $name, int $age): Response
    {
        return json(['name' => $name, 'age' => $age]);
    }
}

You can pass the values of name and age through the GET or POST method, or via route parameters, for example:

Route::any('/user/{name}/{age}', [app\controller\UserController::class, 'create']);

The priority is route parameters > GET > POST parameters.

Default Values

Suppose we access /user/create?name=tom, we will get the following error:

Missing input parameter age

The reason is that we did not pass the age parameter. This can be resolved by setting a default value for the parameter, for example:

<?php
namespace app\controller;
use support\Response;

class UserController
{
    public function create(string $name, int $age = 18): Response
    {
        return json(['name' => $name, 'age' => $age]);
    }
}

Parameter Types

When we access /user/create?name=tom&age=not_int, we will get the following error:

Tip
For convenience in testing, we directly input parameters in the browser address bar. In actual development, parameters should be passed through POST method.

Input age must be of type int, string given

This is because the incoming data will be converted according to type, and if conversion fails, a support\exception\InputTypeException exception will be thrown. Since the passed age parameter cannot be converted to int, the above error occurs.

Custom Errors

We can use multilingual support to customize errors like Missing input parameter age and Input age must be of type int, string given, as referenced in the following command:

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

Other Types

Webman supports parameter types such as int, float, string, bool, array, object, and class instances, for example:

<?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,
        ]);
    }
}

When we access /user/create?name=tom&age=18&balance=100.5&vip=true&extension[foo]=bar, we will get the following result:

{
  "name": "tom",
  "age": 18,
  "balance": 100.5,
  "vip": true,
  "extension": {
    "foo": "bar"
  }
}

Class Instances

Webman supports passing class instances through parameter type hints, for example:

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());
    }
}

When accessing /blog/create?blog[title]=hello&blog[content]=world, we will get the following result:

{
  "title": "hello",
  "content": "world"
}

Model Instances

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;
    // Here you need to add fillable fields to prevent unsafe fields from being passed from the frontend
    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;
    }
}

When accessing /user/create?user[name]=tom&user[age]=18, we will get a result similar to:

1

Controller Lifecycle

When controller_reuse in config/app.php is set to false, a corresponding controller instance will be initialized for each request, and the controller instance will be destroyed after the request ends. This is similar to the operational mechanism of traditional frameworks.

When controller_reuse in config/app.php is set to true, all requests will reuse the controller instance, meaning the controller instance stays in memory once created, and all requests reuse it.

Note
When controller reuse is enabled, requests should not change any properties of the controller, as these changes will affect subsequent requests. For example:

<?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)
    {
        // This method will retain the model upon the first request update?id=1
        // If delete?id=2 is requested again, it will delete data of id 1
        if (!$this->model) {
            $this->model = Model::find($id);
        }
        return $this->model;
    }
}

Tip
Returning data in the controller's __construct() constructor function will have no effect, for example:

<?php
namespace app\controller;

use support\Request;

class FooController
{
    public function __construct()
    {
        // Returning data in the constructor has no effect; the browser will not receive this response
        return response('hello'); 
    }
}

Difference Between Non-reuse and Reuse of Controllers

The differences are as follows:

Non-reuse of Controllers

A new controller instance is created for each request, and the instance is released after the request ends and the memory is reclaimed. Non-reuse of controllers is similar to traditional frameworks, aligning with most developers' habits. Due to the repeated creation and destruction of controllers, the performance may be slightly lower than that of reusing controllers (about 10% lower in helloworld performance tests, which can be largely ignored with business logic).

Reuse of Controllers

With reuse, a controller is instantiated only once per process, and this controller instance is not released after the request ends. Subsequent requests in the current process will reuse this instance. Reusing controllers provides better performance, but it does not conform to most developers' habits.

Situations Where Controller Reuse Cannot Be Used

Controller reuse cannot be enabled when the request modifies the properties of the controller, as these changes will affect subsequent requests.

Some developers prefer to do initialization for each request in the controller's constructor __construct(), in which case controller reuse cannot be used, since the constructor will only be called once per process, not for each request.