Controller

UglyDuckling covers 90% of use cases through JSON resources. When business logic becomes too complex for a JSON resource, you implement a controller.

A controller in UglyDuckling works similarly to a controller in any MVC framework. It implements getRequest() and postRequest() methods, validates and filters incoming parameters, queries the database, and exposes data to a view file.

When the logic inside getRequest()/postRequest() grows complex enough to need isolated testing, extract it into a Service and keep the controller itself limited to validation, delegating to the Service, and mapping the outcome to a view or redirect.


Controller skeleton

class MyController extends BaseController {

    public function __construct() {
        parent::__construct();
        $this->classCompleteName = __CLASS__;
        $this->className         = 'MyController';
        $this->chapter           = 'Website';
        $this->templateFile      = 'websitetemplate';
        $this->viewFile          = 'src/Chapters/Website/Views/MyController';
        $this->controllerPointer = $this;
        $this->appTitle          = 'My website title';
    }

    public function check_authorization_get_request() {
        return true;
    }

    public $get_validation_rules = ['authorid' => 'required|integer'];
    public $get_filter_rules     = ['authorid' => 'trim'];

    public function getRequest() {
    }

    public function check_authorization_post_request() {
        return true;
    }

    public $post_validation_rules = ['title' => 'required|max_len,255'];
    public $post_filter_rules     = ['title' => 'trim|sanitize_string'];

    public function postRequest() {
    }

}

Constructor

A controller must extend BaseController. The constructor sets several properties that the framework uses to route requests, load templates, and render views.

Property Description
$this->classCompleteName Fully qualified class name (__CLASS__). Useful in shared view and template files to identify which controller was called.
$this->className Short class name. Same use as above.
$this->chapter The chapter (domain folder) the controller belongs to. Controllers live at src/Chapters/{Chapter}/Controllers/.
$this->templateFile Name of the template file inside src/Templates/ (without .php).
$this->viewFile Path prefix for the view file. The framework appends Get.php, Post.php, GetError.php, or PostError.php depending on the request.
$this->controllerPointer A reference to $this. Required for the template to access controller properties.
$this->appTitle Application title, typically used in the <title> tag of the template.
public function __construct() {
    parent::__construct();
    $this->classCompleteName = __CLASS__;
    $this->className         = 'MyController';
    $this->chapter           = 'Website';
    $this->templateFile      = 'websitetemplate';
    $this->viewFile          = 'src/Chapters/Website/Views/MyController';
    $this->controllerPointer = $this;
    $this->appTitle          = 'My website title';
}

Each controller must also carry a #[Route] attribute mapping it to a URL slug:

use Fabiom\UglyDuckling\Framework\Routing\Route;

#[Route(name: 'mycontroller', slug: 'mycontroller')]
class MyController extends BaseController {
    // → reachable at www.myapplication.com/mycontroller.html
}

Run vendor/bin/ud-routes generate to pick up the attribute — see Routing for the full generation and dispatch flow.


GET request

check_authorization_get_request

Override this method to restrict which users can make a GET request to this controller. Return true to allow, false to deny. Returning false renders a shared “unauthorized” page (http_response_code(403)) instead of calling getRequest() — same mechanism for both GET and POST, see Unauthorized access below.

For simple group restriction, set $allowedGroups instead of overriding this method — the default implementation already checks it, the same mechanism Component uses. Leaving it empty (the default) means every logged-in group can reach this controller; the same property governs check_authorization_post_request too, so it only needs setting once.

protected array $allowedGroups = ['readergroup', 'writergroup'];

Override the method directly for anything more than a group check:

public function check_authorization_get_request() {
    return isset($_SESSION['group'])
        && in_array($_SESSION['group'], ['readergroup', 'writergroup']);
}

Check ownership via the database:

public function check_authorization_get_request() {
    $bookDao = new BookDao();
    $bookDao->setDBH($this->dbconnection->getDBH());
    $mybook = $bookDao->getOneByFields(['id' => $this->getParameters['id']]);
    return $mybook->author_id === $_SESSION['user_id'];
}

Validation and filter rules

Declare $get_validation_rules and $get_filter_rules as public properties to validate and filter GET parameters before getRequest() is called. The framework runs these automatically and populates $this->getParameters with the cleaned values.

public $get_validation_rules = ['authorid' => 'required|integer'];
public $get_filter_rules     = ['authorid' => 'trim'];

Multiple rules for the same field are separated by a pipe. Rules that take a parameter use a comma:

public $get_validation_rules = [
    'id'   => 'required|integer',
    'slug' => 'required|alpha_numeric_dash|max_len,100',
];
public $get_filter_rules = [
    'id'   => 'trim',
    'slug' => 'trim|sanitize_string|lowercase',
];

See Validation for the full list of available rules and filters.

getRequest

Override this method to query the database and prepare data for the view. Every property you assign on $this becomes a variable available in the view file.

public function getRequest() {
    $bookDao = new BookDao();
    $bookDao->setDBH($this->dbconnection->getDBH());
    $this->books  = $bookDao->getByFields(['author_id' => $this->getParameters['authorid']]);
    $this->author = 'William Shakespeare';
}

View file

The view file renders the data prepared by getRequest(). It is loaded from the path defined in $this->viewFile with Get.php appended — e.g. src/Chapters/Website/Views/MyControllerGet.php.

<h4><?= $author ?></h4>
<ul>
<?php foreach ($books as $book): ?>
    <li><?= htmlspecialchars($book->title) ?></li>
<?php endforeach; ?>
</ul>

Handling validation errors

When GET parameters fail validation the framework calls show_get_error_page() instead of getRequest() and loads MyControllerGetError.php as the view. Override the method to prepare any data the error view needs. The validation error message is available in $this->readableErrors.

public function show_get_error_page() {
    $this->errorMessage = $this->readableErrors;
}
<div class="alert alert-danger"><?= htmlspecialchars($errorMessage) ?></div>

POST request

check_authorization_post_request

Same concept as the GET version, but for POST requests. Return true to allow, false to deny — and $allowedGroups (see above) already covers this method too, so a simple group restriction rarely needs overriding it separately.

public function check_authorization_post_request() {
    return isset($_SESSION['group'])
        && in_array($_SESSION['group'], ['readergroup', 'writergroup']);
}

Validation and filter rules

Declare $post_validation_rules and $post_filter_rules to validate and filter POST parameters before postRequest() is called. Validated values are available in $this->postParameters.

public $post_validation_rules = [
    'title'  => 'required|max_len,255',
    'email'  => 'required|valid_email',
    'price'  => 'required|float|min_numeric,0',
    'avatar' => 'required_file|extension,jpg;jpeg;png;gif',
];
public $post_filter_rules = [
    'title'  => 'trim|sanitize_string',
    'email'  => 'trim|sanitize_email|lowercase',
    'price'  => 'trim|sanitize_floats',
];

See Validation for the full list of available rules and filters.

postRequest

Override this method to process the submitted data — save to the database, send emails, and so on. Every property assigned on $this is available in the view.

public function postRequest() {
    $bookDao = new BookDao();
    $bookDao->setDBH($this->dbconnection->getDBH());
    $bookDao->insert([
        'title'     => $this->postParameters['title'],
        'author_id' => $_SESSION['user_id'],
    ]);
    $this->redirectToPreviousPage();
}

View file

Loaded from $this->viewFile with Post.php appended — e.g. src/Chapters/Website/Views/MyControllerPost.php.

<p>Book saved successfully.</p>

Handling validation errors

When POST parameters fail validation the framework calls show_post_error_page() and loads MyControllerPostError.php. The validation error message is available in $this->readableErrors.

public function show_post_error_page() {
    $this->errorMessage = $this->readableErrors;
}
<div class="alert alert-danger"><?= htmlspecialchars($errorMessage) ?></div>

Unauthorized access

When check_authorization_get_request() or check_authorization_post_request() returns false, the controller renders a shared unauthorized page rather than a redirect — http_response_code(403) plus a static view, the same mechanism used for an unmatched route (see Routing) but with its own template/view and a 403 instead of a 404.

Method Description
show_unauthorized_page() Renders the unauthorized page. Called automatically on authorization failure; override only if you need custom behavior beyond the default.
setUnauthorizedView($templateFile, $viewFile) Overrides the template/view used for the unauthorized page. Defaults to 'staticpage' / 'errors/unauthorized''staticpage' is a minimal built-in template that just requires the view file directly, so write errors/unauthorized.php as a normal, self-contained HTML page.
public function __construct() {
    parent::__construct();
    // ...
    // wrap the unauthorized page in the app's own chrome instead of the bare default
    $this->setUnauthorizedView('websitetemplate', 'errors/websiteunauthorized');
}

This is distinct from an invalid session (not logged in at all), which redirects to the application’s login page before the controller is even reached.


Redirect

After a POST request it is common to redirect rather than render a view. Four helper methods are available:

Method Description
redirectToPreviousPage() Redirect to the last GET request the user made.
redirectToSecondPreviousPage() Redirect to the GET request before that.
redirectToPage($url) Redirect to a specific URL. If the constant BASE_PATH is defined it is prepended automatically.
redirectToDefaultPage() Redirect to the application’s default page defined in DEFAULT_PAGE. BASE_PATH is prepended if defined.
public function postRequest() {
    // ... save data ...
    $this->setSuccess('Record saved successfully.');
    $this->redirectToPreviousPage();
}

Flash messages

You can pass a one-request message to the next page using these methods, which persist the value in the session until it is read:

Method Description
setSuccess($msg) Green success message.
setError($msg) Red error message.
setInfo($msg) Blue informational message.
setWarning($msg) Yellow warning message.

The messages are automatically picked up by the messages block in the next controller’s makeAllPresets() and made available in the view.