jasny/controller
Composer 安装命令:
composer require jasny/controller
包简介
Controller for Slim and other micro-frameworks
关键字:
README 文档
README
PSR-7 controller for Slim Framework and other micro-frameworks.
The controller is responsible handling the HTTP request, manipulate the model and initiate the view.
The code in the controller read as a high level description of each action. The controller should not contain implementation details. This belongs in the model, view or in services and libraries.
Installation
Install using composer
composer require jasny\controller
Setup
Jasny\Controller can be used as a base class for each of your controllers. It lets you interact with the
PSR-7 server request and response in a friendly matter.
class MyController extends Jasny\Controller\Controller { public function hello(string $name, #[QueryParam] string $others = ''): void { $this->output("Hello $name" . ($others ? " and $others" : ""), 'text'); } }
Visiting
https://example.com/hello/Arnold&others=friendswould outputHello Arnold and friends.
Actions are defined as public methods of the controller.
A controller is a callable object by implementing the __invoke method. The invoke method takes a PSR-7
server request and response object and will return a modified response object. This all is abstracted away when you
write your controller.
A router typically handles the request and chooses the correct controller object to call. The router is also responsible for extracting parameters from the url path and possibly choosing a method to call within the controller.
Slim framework
Slim is a PHP micro-framework that works with PSR-7. To use this library with slim, use the provided middleware.
use Jasny\Controller\Middleware\Slim as ControllerMiddleware; use Slim\Factory\AppFactory; $app = AppFactory::create(); $app->add(new ControllerMiddleware()); $app->addRoutingMiddleware(); $app->get('/hello/{name}', ['MyController', 'hello']);
Optionally, the middleware can convert error responses from the controller to Slim HTTP Errors by passing true to the
middleware constructor.
use Jasny\Controller\Middleware\Slim as ControllerMiddleware; use Slim\Factory\AppFactory; $app = AppFactory::create(); $app->add(new ControllerMiddleware(true)); $app->addRoutingMiddleware(); $app->addErrorMiddleware(true, true, true);
Relay + SwitchRoute
SwitchRoute, a super-fast router based on generating code. The router needs a PSR-15 request handler to work with PRS-7 server requests, like Relay.
By default, the route action is converted to the method that will be called by the PSR-15 handler. For this library,
__invoke should be called instead. The invoke method will take care of calling the right method within the controller.
$stud = fn($str) => strtr(ucwords($str, '-'), ['-' => '']); $invoker = new Invoker(fn (?string $controller, ?string $action) => [ $controller !== null ? $stud($controller) . 'Controller' : $stud($action) . 'Action', '__invoke' ]);
See SwitchRoute for more information
Output
When using PSR-7, you shouldn't use echo, because it makes it harder to write tests. Instead, use the output method
of the controller, which writes to the response body stream object.
$this->output('Hello world');
A second argument may be passed, which sets the Content-Type header. You can pass a mime type like 'text/html'.
Alternatively you can use a common file extension like 'txt'. The controller uses the
ralouphie/mimey library to get the mime type.
class MyController extends Jasny\Controller\Controller { /** * Output a random number between 0 and 100 as HTML */ public function random() { $number = rand(0, 100); $this->output("<h1>$number</h1>", 'html'); } }
JSON
The json method can be used to serialize and output data as JSON.
class MyController extends Jasny\Controller\Controller { /** * Output 5 random numbers between 0 and 100 as JSON */ public function random() { $numbers = array_map(fn() => rand(0, 100), range(1, 5)); $this->json($numbers); } }
Response status
To set the response status you can use the status() method. This method can take the response status as integer or
as string specifying both the status code and phrase.
class MyController extends Jasny\Controller\Controller { public function process(string $size) { if (!in_array($size, ['XS', 'S', 'M', 'L', 'XL'])) { return $this ->status("400 Bad Request") ->output("Invalid size: $size"); } // Create something ... return $this ->status(201) ->header("Location: http://www.example.com/foo/something") ->json($something); } }
Alternatively and preferably you can use helper method to set a specific response status. Some method can optionally take arguments that make sense for that status.
class MyController extends Jasny\Controller\Controller { public function process(string $size) { if (!in_array($size, ['XS', 'S', 'M', 'L', 'XL'])) { return $this->badRequest()->output("Invalid size: $size"); } // Create something ... return $this ->created("http://www.example.com/foo/something") ->json($something); } }
The following methods for setting the output status are available
| status code | method | |
|---|---|---|
| 200 | ok() |
|
| 201 | created(?string $location = null) |
Optionally set the Location header |
| 202 | accepted() |
|
| 204/205 | noContent(int $code = 204) |
|
| 206 | partialContent(int $rangeFrom, int $rangeTo, int $totalSize) |
Set the Content-Range and Content-Length header |
| 30x | redirect(string $url, int $code = 303) |
Url for the Location header |
| 303 | back() |
Redirect to the referer |
| 304 | notModified() |
|
| 40x | badRequest(int $code = 400) |
|
| 401 | unauthorized() |
|
| 402 | paymentRequired() |
|
| 403 | forbidden() |
|
| 404/405/410 | notFound(int $code = 404) |
|
| 406 | notAcceptable() |
|
| 409 | conflict() |
|
| 429 | tooManyRequests() |
|
| 5xx | error(int $code = 500) |
- Some methods take a
$messageargument. This will set the output. - If a method takes a
$codeargument, you can specify the status code. - The
back()method will redirect to the referer, but only if the referer is from the same domain as the current url.
Sometimes it's useful to check the status code that has been set for the response. This can be done with the
getStatusCode() method. In addition, there are methods to check the type of status.
| status code | method |
|---|---|
| 1xx | isInformational() |
| 2xx | isSuccessful() |
| 3xx | isRedirection() |
| 4xx | isClientError() |
| 5xx | isServerError() |
| 4xx or 5xx | isError() |
Response headers
You can set the response header using the setResponseHeader() method.
class MyController extends Jasny\Controller\Controller { public function process() { $this->header("Content-Language", "nl"); // ... } }
By default, response headers are overwritten. In some cases you want to have duplicate headers. In that case set the
third argument to true, eg header($header, $value, true).
$this->header("Cache-Control", "no-cache"); // overwrite header $this->header("Cache-Control", "no-store", true); // add header
Input
With PSR-7, you shouldn't use super globals $_GET, $_POST, $_COOKIE, and $_SERVER. Instead, these values are
available through the server request object. This is done using PHP attributes.
| Attribute | Arguments | |
|---|---|---|
PathParam |
name, type | Path parameter obtained from router |
QueryParam |
name, type | Query parameter |
Query |
All query parameters | |
BodyParam |
name, type | Body parameter |
Body |
All body parameters or raw body as string | |
Cookie |
name, type | Cookie parameter |
Cookies |
All cookies as key/value | |
UploadedFile |
name | PSR-7 uploaded file(s) |
UploadedFiles |
Associative array of all uploaded files | |
Header |
name, type | Request header (as string) |
Headers |
All headers as associative array | |
Attr |
name, type | PSR-7 request attribute set by middleware |
The controller will map each argument of a method to a parameter. By default, arguments are mapped to path parameters.
Parameters
Path parameters
A router may extract parameters from the request URL. In the following example, the url path /hello/world,
the path parameter name will have the value "world".
$app->get('/hello/{name}', ['MyController', 'hello']);
The name parameter will be passed as argument to the hello method.
class MyController extends Jasny\Controller\Controller { public function hello(string $name) { $this->output("Hello $name"); } }
Single request parameter
The controller will pass PSR-7 request parameters as arguments. This is specified by an attribute
QueryParamBodyParamCookieUploadedFileHeader
If the argument name is used as parameter name
- for
QueryParam, underscores are replaced with dashes. Eg:$foo_barwill translate to query paramfoo-bar. - for
Header, words are capitalized and underscores become dashes. Eg:$foo_bartranslates to headerFoo-Bar.
All request parameters
To get all request parameters of a specific type, the following attributes are available.
QueryBodyCookiesUploadedFilesHeaders
For the Body attribute, the type of the argument should either be an array or a string. If an array is passed the
argument will be the parsed body. In case of a string it will be the raw body.
PSR-7 request attribute
Middleware can set attributes of the PSR-7 request. These request attributes are available as arguments by using the
Attr attribute.
Parameter name
For single parameters, the name of the argument will be used as parameter name. Alternatively, it's possible to specify a name when defining the attribute.
use Jasny\Controller\Controller; use Jasny\Controller\Parameter\PathParam; use Jasny\Controller\Parameter\QueryParam; class MyController extends Controller { public function hello(#[PathParam] string $name, #[QueryParam('and')] string $other = '') { $this->output("Hello $name" . ($other ? " and $other" : "")); } }
Note: #[PathParam] could be omitted, since it's the default behaviour.
Parameter type
It's possible to specify a type as second argument when defining the attribute. By default, the type is determined on the type of the argument.
use Jasny\Controller\Controller; use Jasny\Controller\Parameter\BodyParam; class MyController extends Controller { public function send(#[BodyParam(type: 'email')] string $emailAddress) { // ... } }
Parameter attributes use the filter_var function to sanitize input. The following
filters are defined
| type | filter |
|---|---|
| bool | FILTER_VALIDATE_BOOL |
| int | FILTER_VALIDATE_INT |
| float | FILTER_VALIDATE_FLOAT |
FILTER_VALIDATE_EMAIL |
|
| url | FILTER_VALIDATE_URL |
For other types (like string), no filter is applied.
use Jasny\Controller\Controller; use Jasny\Controller\Parameter\PostParam; class MyController extends Controller { public function message(#[PostParam(type: 'email')] array $email) { // ... } }
To add custom types, add filters to SingleParameter::$types
use Jasny\Controller\Parameter\SingleParameter; SingleParameter::$types['slug'] = [FILTER_VALIDATE_REGEXP, '/^[a-z\-]+$/'];
Content negotiation
Content negotiation allows the controller to give different output based on Accept request headers. It can be used to
select the content type (switch between JSON and XML), the content language, encoding, and charset.
| Method | Request header | Response header |
|---|---|---|
negotiateContentType() |
Accept |
Content-Type |
negotiateLanguage() |
Accept-Language |
Content-Language |
negotiateEncoding() |
Accept-Encoding |
Content-Encoding |
negotiateCharset() |
Accept-Charset |
negotiateCharset() will modify the Content-Type header if it's already set. Otherwise, it will just
return the selected charset.
The negotiate method takes a list or priorities as argument. It sets the response header and returns the selected option.
class MyController extends Jasny\Controller\Controller { public function hello() { $language = $this->negotiateLanguage(['en', 'de', 'fr', 'nl;q=0.6']); switch ($language) { case 'en': return $this->output('Good morning'); case 'de': return $this->output('Guten Morgen'); case 'fr': return $this->output('Bonjour'); case 'nl': return $this->output('Goedemorgen'); default: return $this ->notAcceptable() ->output("This content isn't available in your language"); } } }
For more information, please check the documentation of the willdurand/negotiation library.
Hooks
In addition to the action method, the controller will also call the before() and after() method.
Before
The before() method is call prior to the action method. If it returns a response, the method action is never called.
class MyController extends Jasny\Controller\Controller { protected function before() { if ($this->auth->getUser()->getCredits() <= 0) { return $this->paymentRequired()->output("Sorry, you're out of credits"); } } // ... }
Instead of before() consider using guards.
After
The after() method is called after the action, regardless of the action response type.
class MyController extends Jasny\Controller\Controller { // ... protected function after() { $this->header('X-Available-Credits', $this->auth->getUser()->getCredits()); } }
Guards
Guards are PHP Attributes that are invoked before the controller method is called. A guard is similar to middleware, though more limited. The purpose of using a guard is to check if the controller action may be executed. If the guard returns a response, that response is emitted and the method on the controller is never called.
class MyController extends Jasny\Controller\Controller { #[MustBeLoggedIn] public function send() { // ... } }
A guard class should implement the process method. A guard class has the same methods as a controller class. The
process method can have input parameters.
use Jasny\Controller\Guard; use Jasny\Controller\Parameter\Attr; #[\Attribute] class MustBeLoggedIn extends Guard { public function process(#[Attr] User? $sessionUser) { if ($sessionUser === null) { return $this->forbidden()->output("Not logged in"); } } }
Order of execution
Guards may be defined on the controller class or the action method. The order of execution is
- Class guards
before()- Method guards
- Action
after()
Dependency injection
Guards are attributes, which are instantiated using PHP reflection. Parameters can be specified when the guard is declared.
#[MinimalCredits(value: 20)] class MyController extends \Jasny\Controller\Controller { // ... }
This makes it difficult to make a service (like a DB connection) available to a guard using dependency injection.
Some DI container libraries, like PHP-DI, are able to inject services to an already instantiated
object. To utilize this, overwrite the Guardian class and register it to the container.
use Jasny\Controller\Guardian; use Jasny\Controller\Guard; use DI\Container; return [ Guardian::class => function (Container $container) { return new class ($container) extends Guardian { public function __construct(private Container $container) {} public function instantiate(\ReflectionAttribute $attribute): Guard { $guard = $attribute->newInstance(); $this->container->injectOn($guard); return $guard; } } } ];
The guard class can use #[Inject] attributes or @Inject annotations.
use Jasny\Controller\Guard; use DI\Attribute\Inject; class MyGuard extends Guard { #[Inject] private DBConnection $db; // ... }
Make sure the Guardian service is injected into the controller using dependency injection.
use Jasny\Controller\Controller; use Jasny\Controller\Guardian; class MyController extends Controller { public function __construct( protected Guardian $guardian ) {} }
jasny/controller 适用场景与选型建议
jasny/controller 是一款 基于 PHP 开发的 Composer 扩展包,目前已累计 9.24k 次下载、GitHub Stars 达 1, 最近一次更新时间为 2016 年 11 月 21 日, 在 PHP 生态内属于活跃度较高的组件。
它主要适用于以下技术方向: 「mvc」 「controller」 等业务场景。在实际项目中,围绕这些方向常见需要落地的问题包括:接口对接、性能调优、并发安全、与既有框架(Laravel / ThinkPHP / Yii / Webman 等)的兼容适配,以及生产环境的日志埋点与稳定性保障。
我们在过去多个企业项目中使用过 jasny/controller 或与其功能相近的方案,如果你在选型或落地过程中遇到问题,例如 版本兼容、二次改造、私有化封装、与内部系统对接、生产 BUG 排查,欢迎联系我们协助评估。
基于 jasny/controller 在你已有业务上做功能扩展、字段裁剪、UI 适配、与内部账号 / 权限 / 日志系统的深度对接。
线上偶发问题、内存泄漏、慢查询、并发异常等排查修复;针对高流量场景做缓存、队列、索引层面的调优。
承接完整的项目从需求 → 设计 → 开发 → 上线 → 长期运维;也可按月提供技术保姆服务。
与 jasny/controller 相关的其它包
同方向 / 同关键字的高下载量 PHP Composer 包推荐,方便对比选型:
Give your JS App some Backbone with Models, Views, Collections, and Events.
Kinikit - PHP Application development framework MVC component
Create mail from controller action render
Extended auth components for Yii2
Small extensible framework in PHP
Oauth 2 client for Ubiquity framework
统计信息
- 总下载量: 9.24k
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 1
- 点击次数: 12
- 依赖项目数: 2
- 推荐数: 1
其他信息
- 授权协议: MIT
- 更新时间: 2016-11-21