Controllers
In the API skeleton, application controllers live in app/Controllers/ and usually extend Glueful\Controllers\BaseController.
Basic Controller
<?php
declare(strict_types=1);
namespace App\Controllers;
use Glueful\Controllers\BaseController;
use Glueful\Http\Response;
use Symfony\Component\HttpFoundation\Request;
class UserController extends BaseController
{
public function index(Request $request): Response
{
$users = db($this->getContext())->table('users')->get();
return $this->success($users);
}
}
Registering Controllers
The router resolves [Controller::class, 'method'] handlers from the container, and there is no autowire fallback for unregistered classes. Register every controller in a provider's services() — in the skeleton, app/Providers/AppServiceProvider.php:
public static function services(): array
{
return [
\App\Controllers\WelcomeController::class => ['autowire' => true, 'shared' => true],
\App\Controllers\UserController::class => ['autowire' => true, 'shared' => true],
];
}
An unregistered controller fails with "Service not found" when its route is first hit. php glueful scaffold:controller writes the class to app/Controllers (namespace App\Controllers) and prints the registration step, but it doesn't register the controller or add a route for you.
What BaseController Gives You
BaseController provides:
$this->getContext()/$this->context— theApplicationContext, for helpers likedb($this->getContext()),app(...),config(...)$this->currentUser— the authenticated?Glueful\Auth\UserIdentity(see Authentication)$this->request— the current Symfony request$this->authManagerand$this->repositoryFactory- response helpers:
success(),created(),notFound(),unauthorized(),forbidden(),validationError(),serverError(),paginated(), plus HTTP-caching helpers likecached()andcheckNotModified() - request helpers:
getRequestData()andgetPutData()(JSON or form bodies) - lightweight validation via
validateRequest()(required,email,max:N)
There is no $this->db property — use db($this->getContext()) or inject Glueful\Database\Connection.
Create / Update Example
class TaskController extends BaseController
{
public function store(Request $request): Response
{
$data = $this->getRequestData();
if ($error = $this->validateRequest($data, [
'title' => 'required|max:255',
'description' => 'max:1000',
])) {
return $error;
}
// insert() returns the affected-row count; insertGetId() returns the new id
$taskId = db($this->getContext())->table('tasks')->insertGetId([
'title' => $data['title'],
'description' => $data['description'] ?? null,
]);
return $this->created([
'id' => $taskId,
'title' => $data['title'],
]);
}
}
For anything beyond simple checks, accept a typed request DTO instead of calling validateRequest().
Constructor Injection
Registered with 'autowire' => true, a controller gets its constructor dependencies from the container:
use Glueful\Bootstrap\ApplicationContext;
use Glueful\Database\Connection;
use Psr\Log\LoggerInterface;
class UserController extends BaseController
{
public function __construct(
ApplicationContext $context,
private readonly Connection $connection,
private readonly LoggerInterface $logger,
) {
parent::__construct($context);
}
public function index(Request $request): Response
{
$this->logger->info('Listing users');
return $this->success($this->connection->table('users')->get());
}
}
If you override the constructor, call parent::__construct($context) so the base dependencies are initialized.
ResourceController
Glueful also ships a built-in Glueful\Controllers\ResourceController behind generic /data/{table} CRUD routes (under the API prefix). Use it when you want convention-driven resource endpoints quickly, but prefer explicit controllers first while you are learning the framework.
For most new applications, start with explicit controllers in app/Controllers and move to generic resource wiring only when the tradeoff is clear.