Component
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
$panelsor$tabsarray 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 }
}