PHPackages                             jasny/controller - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Framework](/categories/framework)
4. /
5. jasny/controller

ActiveLibrary[Framework](/categories/framework)

jasny/controller
================

Controller for Slim and other micro-frameworks

v2.0.0(3y ago)19.1k↓33.3%1[4 issues](https://github.com/jasny/controller/issues)1MITPHPPHP &gt;=8.0.0

Since Nov 21Pushed 2y agoCompare

[ Source](https://github.com/jasny/controller)[ Packagist](https://packagist.org/packages/jasny/controller)[ RSS](/packages/jasny-controller/feed)WikiDiscussions master Synced 2mo ago

READMEChangelog (6)Dependencies (9)Versions (10)Used By (1)

Jasny Controller
================

[](#jasny-controller)

[![PHP](https://github.com/jasny/controller/actions/workflows/php.yml/badge.svg)](https://github.com/jasny/controller/actions/workflows/php.yml)[![Scrutinizer Code Quality](https://camo.githubusercontent.com/091dd4892e403786406805bea56a5e5eff6f5c524800baa21aadd67a29891147/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6a61736e792f636f6e74726f6c6c65722f6261646765732f7175616c6974792d73636f72652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/jasny/controller/?branch=master)[![Code Coverage](https://camo.githubusercontent.com/f8fdefde0283931bb9e7ba28e947f34eed9f0174251608f06e5a6933778975bd/68747470733a2f2f7363727574696e697a65722d63692e636f6d2f672f6a61736e792f636f6e74726f6c6c65722f6261646765732f636f7665726167652e706e673f623d6d6173746572)](https://scrutinizer-ci.com/g/jasny/controller/?branch=master)[![Packagist Stable Version](https://camo.githubusercontent.com/b862e73893cbecc9f4d11141d21b421864a227823867e6332ecdd0397007f7c5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6a61736e792f636f6e74726f6c6c65722e737667)](https://packagist.org/packages/jasny/controller)[![Packagist License](https://camo.githubusercontent.com/7f91f9f30441d7071128e2b237ee89e0adcd929f119491377b07c41479a7ab07/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6a61736e792f636f6e74726f6c6c65722e737667)](https://packagist.org/packages/jasny/controller)

PSR-7 controller for [Slim Framework](https://www.slimframework.com/) 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
------------

[](#installation)

Install using composer

```
composer require jasny\controller

```

Setup
-----

[](#setup)

`Jasny\Controller` can be used as a base class for each of your controllers. It lets you interact with the [PSR-7](http://www.php-fig.org/psr/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=friends` would output `Hello Arnold and friends`.

Actions are defined as public methods of the controller.

A controller is a callable object by implementing the [`__invoke`](http://php.net/manual/en/language.oop5.magic.php#object.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-framework)

[Slim](https://www.slimframework.com/) 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

[](#relay--switchroute)

[SwitchRoute](https://github.com/jasny/switch-route), 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](https://relayphp.com/).

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](https://github.com/jasny/switch-route#readme)**

Output
------

[](#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](https://github.com/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("$number", 'html');
    }
}
```

### JSON

[](#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

[](#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 codemethod[200](https://httpstatuses.com/200)`ok()`[201](https://httpstatuses.com/201)`created(?string $location = null)`Optionally set the `Location` header[202](https://httpstatuses.com/202)`accepted()`[204](https://httpstatuses.com/204)/[205](https://httpstatuses.com/205)`noContent(int $code = 204)`[206](https://httpstatuses.com/206)`partialContent(int $rangeFrom, int $rangeTo, int $totalSize)`Set the `Content-Range` and `Content-Length` header[30x](https://httpstatuses.com/303)`redirect(string $url, int $code = 303)`Url for the `Location` header[303](https://httpstatuses.com/303)`back()`Redirect to the referer[304](https://httpstatuses.com/304)`notModified()`[40x](https://httpstatuses.com/400)`badRequest(int $code = 400)`[401](https://httpstatuses.com/401)`unauthorized()`[402](https://httpstatuses.com/402)`paymentRequired()`[403](https://httpstatuses.com/403)`forbidden()`[404](https://httpstatuses.com/404)/[405](https://httpstatuses.com/405)/[410](https://httpstatuses.com/410)`notFound(int $code = 404)`[406](https://httpstatuses.com/406)`notAcceptable()`[409](https://httpstatuses.com/409)`conflict()`[429](https://httpstatuses.com/429)`tooManyRequests()`[5xx](https://httpstatuses.com/500)`error(int $code = 500)`- Some methods take a `$message` argument. This will set the output.
- If a method takes a `$code` argument, 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 codemethod1xx`isInformational()`2xx`isSuccessful()`3xx`isRedirection()`4xx`isClientError()`5xx`isServerError()`4xx or 5xx`isError()`### Response headers

[](#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
-----

[](#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](https://www.php.net/manual/en/language.attributes.overview.php).

AttributeArguments`PathParam`name, typePath parameter obtained from router`QueryParam`name, typeQuery parameter`Query`All query parameters`BodyParam`name, typeBody parameter`Body`All body parameters or raw body as string`Cookie`name, typeCookie parameter`Cookies`All cookies as key/value`UploadedFile`namePSR-7 uploaded file(s)`UploadedFiles`Associative array of all uploaded files`Header`name, typeRequest header (as string)`Headers`All headers as associative array`Attr`name, typePSR-7 request attribute set by middlewareThe controller will map each argument of a method to a parameter. By default, arguments are mapped to path parameters.

### Parameters

[](#parameters)

#### Path 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

[](#single-request-parameter)

The controller will pass PSR-7 request parameters as arguments. This is specified by an attribute

- `QueryParam`
- `BodyParam`
- `Cookie`
- `UploadedFile`
- `Header`

If the argument name is used as parameter name

- for `QueryParam`, underscores are replaced with dashes. Eg: `$foo_bar` will translate to query param `foo-bar`.
- for `Header`, words are capitalized and underscores become dashes. Eg: `$foo_bar` translates to header `Foo-Bar`.

#### All request parameters

[](#all-request-parameters)

To get all request parameters of a specific type, the following attributes are available.

- `Query`
- `Body`
- `Cookies`
- `UploadedFiles`
- `Headers`

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

[](#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

[](#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

[](#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`](https://www.php.net/filter_var) function to sanitize input. The following filters are defined

typefilterbool`FILTER_VALIDATE_BOOL`int`FILTER_VALIDATE_INT`float`FILTER_VALIDATE_FLOAT`email`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)

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.

MethodRequest headerResponse 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](https://github.com/willdurand/Negotiation) library.

Hooks
-----

[](#hooks)

In addition to the action method, the controller will also call the `before()` and `after()` method.

### Before

[](#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() paymentRequired()->output("Sorry, you're out of credits");
        }
    }

    // ...
}
```

*Instead of `before()` consider using guards.*

### After

[](#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)

Guards are [PHP Attributes](https://www.php.net/manual/en/language.attributes.overview.php) 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

[](#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

[](#dependency-injection)

Guards are attributes, which are [instantiated using PHP reflection](https://www.php.net/manual/en/language.attributes.reflection.php). 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](https://php-di.org/), 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
    ) {}
}
```

###  Health Score

31

—

LowBetter than 68% of packages

Maintenance0

Infrequent updates — may be unmaintained

Popularity25

Limited adoption so far

Community11

Small or concentrated contributor base

Maturity74

Established project with proven stability

 Bus Factor1

Top contributor holds 90.1% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~268 days

Recently: every ~517 days

Total

9

Last Release

1317d ago

Major Versions

v1.2.2 → v2.x-dev2022-08-17

PHP version history (2 changes)v1.0.0PHP &gt;=5.6.0

v2.x-devPHP &gt;=8.0.0

### Community

Maintainers

![](https://www.gravatar.com/avatar/3379a93d51305df325df9045e1a8b205d195e4e8c01312dff53a000ee79002eb?d=identicon)[jasny](/maintainers/jasny)

---

Top Contributors

[![jasny](https://avatars.githubusercontent.com/u/100821?v=4)](https://github.com/jasny "jasny (109 commits)")[![Minstel](https://avatars.githubusercontent.com/u/6154708?v=4)](https://github.com/Minstel "Minstel (12 commits)")

---

Tags

phppsr-7slim-frameworkmvccontroller

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/jasny-controller/health.svg)

```
[![Health](https://phpackages.com/badges/jasny-controller/health.svg)](https://phpackages.com/packages/jasny-controller)
```

###  Alternatives

[cakephp/cakephp

The CakePHP framework

8.8k18.5M1.6k](/packages/cakephp-cakephp)[neos/flow

Flow Application Framework

862.0M451](/packages/neos-flow)[martynbiz/slim3-controller

Provides controller functionality to Slim Framework v3. Also includes PHPUnit TestCase for testing controllers.

2814.4k1](/packages/martynbiz-slim3-controller)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
