Skip to main content

Request Handling

Now let's discuss request handling and rendering content by modules.

In Flute, there is nothing ordinary. There are routes and Controllers. Routes themselves are initialized either in the routes.php file or by directly calling the router() method.

Routes

Let's see how the basic structure of routes looks like:

note

We will consider routes in the context of a module (not in the admin panel), so an important note.

Each route must be unique. This means that in the shop module, there cannot be a route with the path /, only shop/ is possible.

<?php

$router->group(function ($router) {
$router->middleware(HasPermissionMiddleware::class);

$router->group(function (RouteGroup $admin) {
$admin->get('/', [IndexView::class, 'index']);
$admin->get('/settings', [MainSettingsView::class, 'index']);

$admin->group(function (RouteGroup $adminModule) {
$adminModule->get('/list', [ModulesView::class, 'list']);
}, '/modules');
});
});

What you can do with routes:

  • Group them
  • Apply Middleware to them
  • Create routes only for a specific method (GET / POST / PUT / DELETE)
  • Create routes that provide only the view() interface
  • And much more...

Each method argument should include:

  • Path to the controller
  • Function

Example of a route with the GET method for the path /somepath:

router()->get('/somepath', [
SomeController::class, // Path to our controller
'index' // Method to be called in this controller on this path
])

I don't see the point in describing each function. You can see what methods exist in routes in the app\Core\Router\RouteGroup.php file.

Controllers

Controllers are classes that handle specific routes. Each controller must inherit the AbstractController class and must contain the necessary methods specified in the router.

Example of implementing a controller returning an interface:

<?php

use Flute\Core\Support\AbstractController;

class HomeController extends AbstractController
{
public function index()
{
// Return our interface as a Response
return view('Modules/Monitoring/Resources/Views/servers');
}
}

Certainly, controllers can also handle API requests. Here's an example of implementing an API method:

<?php

use Flute\Core\Support\AbstractController;

class ApiController extends AbstractController
{
public function edit(FluteRequest $request, $id)
{
$entity = rep(Entity::class)->findByPK((int) $id);

$entity->name = $request->name;

transaction($entity)->run();

return $this->success();
}
}

I understand you see a slightly non-standard scheme.

First, let's clarify that each argument inside the controller method is complemented by DI (Dependency Injection), allowing you to import custom classes or services directly into the method.

$id - This is {id} defined inside the route, which is automatically passed to the controller by name.

note

Here's how the route would look like:

router()->put('/some/edit/{id}', [ApiController::class, 'edit']);

In this example, we are changing the name of some entity, and then returning success().

Yes, in AbstractController, there are many methods that make it easier to create controllers. There are error(), json(), and others.

You can open this file and manually see everything documented there.

Interface

The principle of implementing interfaces is described here.

Tips

Perhaps I'll give you some tips that I recommend following when creating routes and controllers.

1. Always separate logic from view

It's important to create controllers that contain only those methods specified in the class name.

For example: We have functionality for rendering and adding an item. To implement this task, we need two routes:

  • GET - /list/
  • POST - /list/add

Here's how the routes would look like:

router()->group(function(RouteGroup $router) {
$router->get('/', [ViewController::class, 'index']);

$router->post('/add', [ApiController::class, 'add']);
}, '/list');

As you can see, I have separated them into 2 distinct classes:

  • ViewController
  • ApiController

Each of them is responsible for a specific task.

  • ViewController - only responsible for rendering content on the page
  • ApiController - responsible only for handling requests

2. Split controllers

I strongly recommend splitting your controller into services (Service) as your logic grows.

Why? - It's simple

If the controller contains a large amount of logic, it will be simply impossible to maintain. Your code will turn into some kind of monolith that will make you feel sick.

So create arbitrary services and call them inside the controller.

ApiController.php
public function someAction(FluteRequest $request, UserService $user)
{
// We catch the exception from the service.
// This is not necessary, just as a demonstration
try {
$user->someFunc($request->input());

return $this->success();
} catch (\Exception $e) {
return $this->error($e->getMessage());
}
}

And the service itself:

UserService.php
class UserService
{
public function someFunc(array $args)
{
if( !$args ) throw new \Exception('Args is empty');

/** Anything can go here */

return true;
}
}

3. Don't shy away from Middleware

Middleware - it's like a Firewall used to facilitate checks inside controllers.

Middleware itself looks like this:

<?php

use Flute\Core\Support\AbstractMiddleware;
use Flute\Core\Support\FluteRequest;

class CSRFMiddleware extends AbstractMiddleware
{
public function __invoke(FluteRequest $request, \Closure $next)
{
// Example CSRF check
if( !template()->getBlade()->csrfIsValid() )
return $this->error(__('def.csrf_expired'));

return $next($request);
}
}

You can separate Middleware for authorization check, product existence, user existence, etc.

You can define Middleware in a route group:

router()->group(function ($routeGroup) {
$routeGroup->middleware(CSRFMiddleware::class);
});

Or directly in the controller:

ApiController.php
class ApiController
{
public function __construct()
{
$this->middleware(CSRFMiddleware::class);
}
}

In general, it's a useful thing. Make sure to use it in your projects.