Description

A Component is a PHP class that encapsulates a single, self-contained unit of a page: it fetches data, validates input, handles POST submissions, and renders its own HTML.

Every component extends BaseComponent and must implement the abstract method render(array $data).

A component can be used in two ways:

  • Embedded in a page — a Grid Page or Tabs Page declares it in its $panels or $tabs array alongside other components.
  • Standalone — the component class is registered directly in $index_components. The bootstrap detects that it is not a page and wraps it automatically in a full-width grid so no explicit page class is needed.

For a step-by-step walkthrough of all four CRUD operations using components, see the CRUD with Components tutorial.


Minimal example

use Fabiom\UDDemo\Components\BaseComponent;

class ArticlesList extends BaseComponent {

    protected function get_request(): array {
        return [
            'articles' => $this->executeSelectQuery(
                "SELECT art_id, art_title, art_created FROM articles ORDER BY art_created DESC",
                []
            ),
        ];
    }

    public function render(array $data): void { ?>
        <div class="card">
            <div class="card-header"><h5 class="mb-0">Articles</h5></div>
            <div class="card-body p-0">
                <table class="table table-sm mb-0">
                    <thead>
                        <tr><th>Title</th><th>Date</th></tr>
                    </thead>
                    <tbody>
                        <?php foreach ($data['articles'] as $row): ?>
                            <tr>
                                <td><?= htmlspecialchars($row['art_title']) ?></td>
                                <td><?= htmlspecialchars($row['art_created']) ?></td>
                            </tr>
                        <?php endforeach; ?>
                    </tbody>
                </table>
            </div>
        </div>
    <?php }

}

GET: fetching data

Override get_request() to load data from the database. The returned array is passed as $data to render().

protected function get_request(): array {
    return [
        'articles' => $this->executeSelectQuery(
            "SELECT art_id, art_title FROM articles WHERE art_authorid = :authorid",
            ['authorid' => $_SESSION['user_id']]
        ),
    ];
}

Validating GET parameters

Declare $get_validation_rules and $get_filter_rules to validate URL parameters before using them. Validated values land in $this->getParameters.

protected array $get_validation_rules = [
    'art_id' => 'required|max_len,38',
];
protected array $get_filter_rules = [
    'art_id' => 'trim',
];

protected function get_request(): array {
    if (empty($this->getParameters)) {
        return [];
    }
    $rows = $this->executeSelectQuery(
        "SELECT art_id, art_title, art_body FROM articles WHERE art_id = :art_id",
        $this->getParameters
    );
    return $rows[0] ?? [];
}

Validation rules follow the GUMP library syntax. If validation fails, $this->getParameters is empty and render() receives an empty array.


POST: handling form submissions

Override post_request() to write data to the database. Declare $post_validation_rules and $post_filter_rules to validate POST input. Validated values land in $this->postParameters.

protected array $post_validation_rules = [
    'art_id'    => 'required|max_len,38',
    'art_title' => 'required|max_len,200',
];
protected array $post_filter_rules = [
    'art_title' => 'trim|sanitize_string',
];

protected function post_request(): void {
    $this->executeWriteQuery(
        "UPDATE articles SET art_title = :art_title WHERE art_id = :art_id",
        $this->postParameters
    );
}

Routing POST submissions to the right component

A page can host several components. When a form is submitted, the framework needs to know which component should handle it. Include a hidden field named _component with the fully-qualified class name:

<form method="post">
    <input type="hidden" name="_component" value="<?= self::class ?>">
    <!-- form fields -->
    <button type="submit" class="btn btn-primary">Save</button>
</form>

The page’s POST dispatcher reads $_POST['_component'] and delegates to the matching component.

Redirecting after a successful POST

Set $postSuccessUrl to redirect after a successful submission. Set $postSuccessMessage to store a flash message in the session.

public string $postSuccessMessage = 'Article saved successfully';

public function __construct() {
    $this->postSuccessUrl = url_for('articles-list');
}

Database helpers

BaseComponent provides two protected query helpers that handle PDO preparation and parameter binding automatically.

executeSelectQuery

Returns an array of associative arrays (one per row).

$rows = $this->executeSelectQuery(
    "SELECT art_id, art_title FROM articles WHERE art_authorid = :authorid",
    ['authorid' => $_SESSION['user_id']]
);

executeWriteQuery

Executes an INSERT, UPDATE, or DELETE. Returns void; throws on failure.

$this->executeWriteQuery(
    "UPDATE articles SET art_title = :title WHERE art_id = :id",
    ['title' => $this->postParameters['art_title'], 'id' => $this->postParameters['art_id']]
);

Registering a component as a standalone page

When a component is the only thing on a screen, there is no need to create an explicit page class. Register the component class directly in index_components.php and the bootstrap wraps it automatically in a full-width (col-md-12) grid:

// index_components.php
$index_components = [
    'article-edit'   => ArticleEdit::class,
    'article-delete' => ArticleDelete::class,
];

The bootstrap also accepts an inline panels array or a tabs array as the value, so a layout can be described without writing any class at all:

$index_components = [
    // inline grid — no class needed
    'articles-page' => [
        ['cssclass' => 'col-12 mb-4', 'component' => ArticlesList::class],
        ['cssclass' => 'col-md-8 offset-md-2', 'component' => ArticleNew::class],
    ],
    // inline tabs — no class needed
    'articles-tabbed' => ['tabs' => [
        ['id' => 'tab-list', 'label' => 'Articles', 'panels' => [
            ['cssclass' => 'col-12', 'component' => ArticlesList::class],
        ]],
        ['id' => 'tab-new', 'label' => 'New Article', 'panels' => [
            ['cssclass' => 'col-md-8 offset-md-2', 'component' => ArticleNew::class],
        ]],
    ]],
];

An explicit page class (extending BaseGridComponent or BaseTabsComponent) is only necessary when the page needs custom authorization logic, a custom onPostSuccess() redirect, or page-level POST handling beyond what the components themselves provide.


Authorization

Override check_authorization_resource_request() to restrict who can see the component. Return false to silently skip rendering.

protected function check_authorization_resource_request(): bool {
    return isset($_SESSION['group']) && $_SESSION['group'] === 'admin';
}

CSS and JavaScript

Override addToHead() to inject into the <head> section, and addToFoot() to inject just before </body>. These are called by the parent page, which deduplicates calls automatically.

public function addToHead(): string {
    return '<link rel="stylesheet" href="vendor/datatables/dataTables.bootstrap5.min.css">';
}

public function addToFoot(): string {
    return '<script src="vendor/datatables/dataTables.min.js"></script>';
}

Complete example: an edit form component

use Fabiom\UDDemo\Components\BaseComponent;

class ArticleEdit extends BaseComponent {

    public string $postSuccessMessage = 'Article updated';

    public function __construct() {
        $this->postSuccessUrl = url_for('articles-list');
    }

    protected array $get_validation_rules = ['art_id' => 'required|max_len,38'];
    protected array $get_filter_rules     = ['art_id' => 'trim'];

    protected array $post_validation_rules = [
        'art_id'    => 'required|max_len,38',
        'art_title' => 'required|max_len,200',
        'art_body'  => 'required',
    ];
    protected array $post_filter_rules = [
        'art_title' => 'trim|sanitize_string',
        'art_body'  => 'trim',
    ];

    protected function get_request(): array {
        if (empty($this->getParameters)) {
            return [];
        }
        $rows = $this->executeSelectQuery(
            "SELECT art_id, art_title, art_body FROM articles WHERE art_id = :art_id",
            $this->getParameters
        );
        return $rows[0] ?? [];
    }

    protected function post_request(): void {
        $this->executeWriteQuery(
            "UPDATE articles SET art_title = :art_title, art_body = :art_body WHERE art_id = :art_id",
            $this->postParameters
        );
    }

    public function render(array $data): void { ?>
        <div class="card">
            <div class="card-header"><h5 class="mb-0">Edit Article</h5></div>
            <div class="card-body">
                <form method="post">
                    <input type="hidden" name="_component" value="<?= self::class ?>">
                    <input type="hidden" name="art_id"
                           value="<?= htmlspecialchars($data['art_id'] ?? '') ?>">
                    <div class="mb-3">
                        <label class="form-label">Title</label>
                        <input type="text" class="form-control" name="art_title"
                               value="<?= htmlspecialchars($data['art_title'] ?? '') ?>">
                    </div>
                    <div class="mb-3">
                        <label class="form-label">Body</label>
                        <textarea class="form-control" name="art_body" rows="6"><?=
                            htmlspecialchars($data['art_body'] ?? '')
                        ?></textarea>
                    </div>
                    <button type="submit" class="btn btn-primary">Save</button>
                </form>
            </div>
        </div>
    <?php }

}