taeluf / phad
Framework for integrating html, sql, & php into a html-based views
Requires
- taeluf/cli: v0.1.x-dev
- taeluf/lildb: v0.1.x-dev
- taeluf/phtml: v0.1.x-dev
Requires (Dev)
- league/commonmark: ^1.0
- taeluf/code-scrawl: v0.8.x-dev
- taeluf/liaison: v0.7.x-dev
- taeluf/lildb: v0.1.x-dev
- taeluf/tester: v0.3.x-dev
- taeluf/user-gui: v0.4.x-dev
Suggests
- league/commonmark: ^1.0 to use commonmark filter
- taeluf/liaison: Require v0.7.x-dev for the built-in Liaison integration for routing
- taeluf/user-gui: Require v0.4.x-dev for built-in User-access integration
This package is auto-updated.
Last update: 2025-12-12 19:12:08 UTC
README
Phad: Php Html Api Database
Write HTML templates and auto-fill it from the database. Generate routes, handle form submissions with validation, and even validate logged-in-users all through HTML (and a little SQL). Your Templates can include PHP but it is often not necessary.
The templates you write are compiled into PHP scripts to improve runtime performance.
NOTICE: The latest working version is branch v0.4. This branch is NOT READY. The documentation represents what I WANT, not what is actually functional right now.
Install
composer require taeluf/phad v1.0.x-dev
or in your composer.json
{"require":{ "taeluf/phad": "v1.0.x-dev"}}
Documentation
- Getting Started (below): Standard setup integrating with Liaison and my User Login library.
- TODO Loading Data: Load data into your views and forms, whether from the database or manually loaded. Optionally load your data into ORMs.
- TODO Routing and Sitemaps: Create routes to your views and forms, and add them to your sitemap.
- TODO Property Filters: Convert the database-value of properties into desired format.
- TODO Handling Errors: Handle expected errors, such as data-not-available or access-not-allowed
- TODO Form Validation: Validate values submitted via forms
- TODO Form Hooks:
onsubmit,failsubmit, anddidsubmithooks for forms - TODO Access Controls: Manage access to views, forms, and data based on logged-in user
- TODO Troubleshooting
Getting Started
With Phad (class Phad), you will write Templates that contain HTML, PHP (optional), and additional markup for Items. An Item (<div item="Blog">) queries your database, then auto-hydrates the template with the loaded data. For example <h1 prop="title"></h1> would auto-fill with the Blog Post's title. Custom property filters can be added (<h1 prop="title" filter="localize_language"></h1>).
Access controls are available for nodes (<button access="role:admin">Delete Post</button>). There is a barebones Access class (class \Phad\Integration\DynamicAccess), you can add your own (interface \Phad\AccessInterface), or use the pre-made User Login integration (class \Phad\Integration\TlfUserAccess).
Queries are defined in your template (<p-data where="Blog.slug = :slug" access="call:post_is_published">), custom data loaders can be used (<p-data data_loader="get_blog_post">), or data can be passed manually to your templates ($phad->item('blog/page', ['Blog'=>$blog_row])). Loaded rows are converted into plain PHP objects, but can be converted into custom objects (override \Phad->object_from_row()). If no rows are found or access is denied, error handlers are available (<on s=404>No Blog Posts Found</on>).
Forms (<form item="Blog">) can be auto-filled based on id (<p-data where="id = :id" access="call:is_owner">), access controls can be added inline (<form item="Blog" candelete="role:admin" cansubmit="call:is_post_owner">). Submission works out-of-the-box if you add routes. Server-side validation rules can be added directly to inputs (<input name="description" minlength="50" maxlength="2000" required>), custom validation rules can be written in PHP, and automatic validation exists for <select>/<option> lists, radio inputs, and based on input type. Hooks (<onsubmit>/<didsubmit>/<diddelete>, etc) can be added to forms.
Routes are added to the top of templates (<route pattern="/blog/{slug}/">), and sitemaps can be auto-generated (<sitemap sql="SELECT slug FROM blog" ...>). When <routes> are present, they're fed to your Router (interface Phad\RouterInterface), and a default integration for Liaison (class \Phad\Integration\LiaisonRouter) is included.
Items are loaded automatically when routed to. Associated CSS & Javascript files are automatically loaded as well (as long as your RouterInterface supports this). You can also load items manually ($phad->item(...)) and display HTML ($item->html()), retrieve data as an array ($item->rows()), submit changes ($item->submit()), delete items ($item->delete()), get their list of css/js files ($item->resource_files()), or generate a create table statement ($item->create_table_statement()).
A single template CAN have multiple items, though it's often easier to create separate templates. The tags <x-item> and <x-prop> can be used to print items/properties directly, without extra HTML being printed. The loop attribute (<div item="Blog" loop="inner">) can be added so the outer HTML only prints once, but the inner HTML prints for every returned row.
Phad is suitable for most forms and views and is extremely customizable, but where complex queries and logic are required, it may be best to handle this in regular PHP, rather than finagle Phad into handling said complexity.
Docs Below
- Server Setup
- Example Item Template
- Example Form
- Load Items Manually
Server Setup
This setup uses Liaison for routing and my User Login library for access controls. You can write a custom Router Class and Access Class for other setups. You may use \Phad\Integration\DynamicAccess as your Access class for simple read-only setups, or use it as a base class and modify only what you need.
/deliver.php: (or wherever you initialize your server)
<?php
// Setup Liaison and User Library
$root_dir = __DIR__;
$pdo = new \PDO(...);
$configs = [...]; // configs are optional
$phad = \Phad::custom(
$pdo,
$root_dir, // $dir will contain sub-dirs `phad/`, `sitemap/`, and `cache/` by default
new \Phad\Integration\LiaisonRouter($liaison),
new \Phad\Integration\TlfUserAccess($user_package),
$configs
);
// $phad->throw_on_query_failure = false; # true by default. Set false for queries to fail silently.
// $phad->force_compile = true; # false by default. Set true while developing and debugging to always re-compile items.
Configuration: See defaults.json. You can either make your own .json config file or write the config array directly in PHP (json is better if you want different configs in different environments). Use vendor/bin/phad setup to generate the config file for you.
Note: For Liaison & TLF User integration, you may call Phad::main($pdo, $root_dir, $liaison, $user_package, $configs) instead of custom(...).
Example Item Template
blog/post.php
<route pattern="/blog/{slug}/"></route>
<div item="Blog">
<p-data where="Blog.slug LIKE :slug"></p-data>
<h1 prop="title"></h1>
<time datetime="<?=$Blog->created_at?>"><?=$Blog->created_at?></time>
<x-prop prop="md_body" filter="markdown_to_html"></x-prop>
</div>
This creates a route to /blog/{slug}/. When the route is requested, the <p-data> node's where is used to build the SQL query. The {slug} from the route is automatically bound to :slug in the where statement.
The <h1> is filled with the blog's title. HTML is rendered from the md_body, and that HTML replaces the <x-prop> node. (<x-prop itself is not rendered)
Example Form
blog/form.php
<route pattern="/form/blog/"></route>
<form item="Blog" target="/blog/{slug}/">
<p-data type="default" where="id = :id" access="call:is_owner"></p-data>
<onsubmit><?php
/* $BlogRow is the submitted data and can be modified here */
$BlogRow['slug'] = \YourNamespace\slugify($BlogRow['title']);
?></onsubmit>
<input type="text" name="title" maxlength="75" />
<textarea name="body" maxlength="1500"></textarea>
<input type="submit" value="Submit" />
<input type="hidden" name="id" />
<!-- Since `slug` is added during `onsubmit`, this `type="backend"/name="slug"` is required in order to pass form validation -->
<input type="backend" name="slug" />
</form>
GET /form/blog/ will display this form. If ?id=37 is passed, then the blog post with id=37 will automatically be filled into the form.
GET /form/blog/?phad_action=delete&id=37 will DELETE the blog post with id=37 ONLY if candelete attribute is added to the <form node AND candelete handler returns true. (Note: This functionality WILL change because DELETE operations should never be over GET, and should never be performed without CSRF validation)
POST /form/blog/ will save the blog post to database, whether INSERTing a new article, or UPDATEing an existing one.
<onsubmit> is a hook that will be executed during submission BEFORE any INSERT/UPDATE operation, allowing you to add a slug or make other modifications before database entry.
Additional hooks are available.
Load Items Manually
Forms and views will be loaded automatically when their routes are requested. You can also manually load items to use them wherever you'd like.
Related: Loading Data
Note: root_dir/blog/form.php is item named blog/form
Retrieve an item:
<?php
// Load using default data (*empty form*)
$item = $phad->item('blog/form', []);
// Load the form, filled by loading an item with id=37
$item = $phad->item('blog/form', ['id'=>37]);
// Load the item, specifying a query paramater (*you can add multiple key/value pairs*)
$item = $phad->item('blog/post', ['slug'=>'bears-are-the-best']);
// Load the item using a named data node. See 'Loading Data' documentation for more information.
$item = $phad->item('blog/post-list', [':data'=>'data-node-name']);
// Pass a database row to item (*this skips per-row access checks*)
$item = $phad->item('blog/post', ['Blog' => $row_from_db]);
// Pass multiple database rows to an item (*skips per-row access checks*)
$item = $phad->item('blog/post-list', ['BlogList' => $multiple_rows_from_db]);
Note: For the last two examples, the item's name is Blog. I.e. <div item="Blog">. The key must match the item's name. Append List to the item's name to pass multiple rows to the template.
Display/Submit/Delete the item:
<?php
// $item->force_compile = false; # set true to always re-compile
// display the item/form
echo $item->html();
// Submit forms. Requires that a single row was passed to it:
// `$item = $phad->item('blog/post', ['Blog' => $_POST])`
$item->submit();
// Delete the item.
$item->delete();
Other manual item operations:
<?php
// Get data as an array. NOTE: Only returns rows of first item in the template.
$rows = $item->rows();
// Get CSS & JS Files `['css'=> [... list of files], 'js'=>[... list of files]]`
$item->resource_files();
// Generate CREATE TABLE statement from forms
$item->create_table_statement();