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:
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.
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.
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:
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:
class ApiController
{
public function __construct()
{
$this->middleware(CSRFMiddleware::class);
}
}
In general, it's a useful thing. Make sure to use it in your projects.