Controllers handle incoming HTTP requests and return responses. They act as the glue between routes, services, and views.
Controllers extend App\Core\BaseController:
# Create a standard controller in the 'Account' module
php dock controller:create Profile Account
# Create an API controller in the 'Account' module
php dock controller:create User Account --apinamespace App\Account\Controllers;
use App\Core\BaseController;
use Helpers\Http\Response;
class TweetController extends BaseController
{
public function index(): Response
{
return $this->asView('tweets');
}
}Anchor encourages a service-first architecture. Business logic should live in service classes, not controllers:
namespace App\Auth\Controllers;
use App\Auth\Views\Models\LoginViewModel;
use App\Core\BaseController;
use Helpers\Http\Response;
class LoginController extends BaseController
{
public function index(LoginViewModel $login_view_model): Response
{
return $this->asView('login', compact('login_view_model'));
}
public function attempt(): Response
{
if (!$this->request->isPost()) {
return $this->response->redirect($this->request->fullRoute());
}
// 1. Smart Validation has already run. Retrieve the validated result.
$login = $this->auth->login($this->request->validated());
// 2. Check login status via AuthResult
if (!$login->isSuccessful()) {
$this->flash->error($login->getMessage() ?? 'Invalid login credentials.');
return $this->response->redirect($this->request->fullRoute());
}
return $this->handleLoginRedirect();
}
private function handleLoginRedirect(): Response
{
$redirect_to = $this->request->fullRouteByName('home');
if (!$this->request->isLoginRoute()) {
$redirect_to = $this->request->callback();
}
return $this->response->redirect($redirect_to);
}
}Type-hint dependencies in controller methods - the IoC container will resolve them automatically:
public function index(UserService $userService, LoginViewModel $viewModel): Response
{
$users = $userService->getAllActiveUsers();
return $this->asView('users', compact('users', 'viewModel'));
}- Services: Business logic (e.g.,
UserService,AuthService) - ViewModels: View-specific data preparation
- Validators: Form/request validation
- Requests: Custom request objects
All controllers extending BaseController have access to several core properties and methods for managing common tasks.
An instance of Helpers\Http\Request, providing access to input data, environment variables, and request state.
An instance of Helpers\Http\Response, used to build and send HTTP responses.
An instance of Helpers\Http\Flash, for managing one-time session messages (feedback, errors).
The AuthServiceInterface instance, handling user authentication and authorization.
Provides access to structured view models like layout_view_model and user_view_model.
asView(string $template, array $data = [], ?callable $callback = null): ResponseRenders a view template located in the controller's module directory. It automatically passes the layout view model to the view.
- Use Case: Displaying a user profile page or a list of items.
- Example:
return $this->asView('profile', ['user' => $user]).
asJson(array $data, int $code = 200): ResponseReturns a JSON response with the appropriate Content-Type header.
- Use Case: Returning data for AJAX requests or mobile app APIs.
- Example:
return $this->asJson(['status' => 'success', 'id' => 1]).
asApiResponse(bool $is_successful, string $message, mixed $data = null, int $status_code = 200): ResponseWraps data in a standardized API structure: ['status' => bool, 'message' => string, 'data' => mixed].
- Use Case: Ensuring consistent API responses across the entire application.
unauthorized(): ResponseA convenience method that returns a 401 Unauthorized JSON response.
Use asView() to render templates:
// Simple view
return $this->asView('tweets/show');
// With data
return $this->asView('tweets/show', ['tweet' => $tweet]);
// With compact
return $this->asView('tweets/show', compact('tweet', 'comments'));Use asJson() for API endpoints:
return $this->asJson([
'status' => 'success',
'data' => $tweet
]);
// With status code
return $this->asJson(['error' => 'Not found'], 404);// Redirect to route
return $this->response->redirect($this->request->fullRoute('tweets'));
// Redirect to named route
return $this->response->redirect($this->request->fullRouteByName('home'));
// Redirect back
return $this->response->back();To keep controllers "thin" and manageable, move complex business logic into Services. This makes your code reusable and easier to test.
Create a class that encapsulates a specific domain logic (e.g., managing users).
namespace App\Services;
use App\Models\User;
use Helpers\Encryption\Drivers\SymmetricEncryptor;
class UserService
{
public function __construct(
private readonly SymmetricEncryptor $encryptor
) {}
public function confirmUser(array $credentials): ?User
{
$user = User::query()->where('email', $credentials['email'])->first();
if (!$user || !$this->encryptor->verifyPassword($credentials['password'], $user->password)) {
return null;
}
return $user;
}
}Type-hint the service in your controller method. The framework will automatically resolve and inject it.
// App/Auth/Controllers/LoginController.php
namespace App\Auth\Controllers;
use App\Services\UserService;
use App\Validations\Form\LoginFormRequestValidation;
use Helpers\Http\Response;
class LoginController extends BaseController
{
public function authenticate(UserService $userService): Response
{
// 1. Smart Validation has already run. Retrieve the validated DTO.
$request = $this->request->validated();
// 2. Delegate logic to the service
$user = $userService->confirmUser($request->toArray());
if (!$user) {
$this->flash->error('Invalid credentials');
return $this->response->redirect($this->request->fullRoute('auth/login'));
}
// Login successful...
}While services are encouraged, you can optionally use the repository pattern for data access:
namespace App\Repositories;
use App\Models\Tweet;
class TweetRepository
{
public function all()
{
return Tweet::all();
}
public function findById(int $id): ?Tweet
{
return Tweet::find($id);
}
public function create(array $data): Tweet
{
return Tweet::create($data);
}
}The framework encourages services over repositories. Use repositories only if your team prefers that pattern.
Use validation classes to validate requests:
namespace App\Auth\Validations\Form;
use Core\BaseRequestValidation;
class LoginFormRequestValidation extends BaseRequestValidation
{
public function expected(): array
{
return ['email', 'password'];
}
public function rules(): array
{
return [
'email' => [
'type' => 'email',
'is_valid' => ['email']
],
'password' => [
'type' => 'string'
]
];
}
public function parameters(): array
{
return [
'email' => 'Email Address',
'password' => 'Password',
];
}
}- Keep controllers thin: Move business logic to services
- Use dependency injection: Type-hint dependencies in methods
- Use services for business logic: Not repositories
- Validate early: Use validation classes
- Use flash messages: Provide user feedback
- Return proper responses: Views, JSON, or redirects
- Handle errors gracefully: Check for failures and provide feedback