Created as simplified and lightweight alternative to other PHP frameworks
/www
into your webserver's public html folder/www/php/config.site.php
(or config.localhost.php
for development)/db/fwdatabase.sql
, /db/lookups.sql
and others (if needed)/logs/osafw.log
/db - initial fwdatabase.sql script and update sql scripts
/logs/osafw.log - application log (ensure to enable write rights to /logs dir for webserver)
/www - application public root folder
/php - all the PHP code is here
/controllers - your controllers
/fw - framework core libs
/models - your models
/vendor - composer libs
/config.*.php - settings for db connection, mail, logging...
/template - all the html templates
/upload - upload dir for public files
/assets - your web frontend assets
/css
/fonts
/img
/js
/favicon.ico - change to your favicon!
/robots.txt - default robots.txt (empty)
Controllers automatically directly mapped to URLs, so developer doesn't need to write routing rules:
GET /Controller
- list view IndexAction()
GET /Controller/ID
- one record view ShowAction()
GET /Controller/new
- one record new form ShowFormAction()
GET /Controller/ID/edit
- one record edit form ShowFormAction()
GET /Controller/ID/delete
- one record delete confirmation form ShowDeleteAction()
POST /Controller
- insert new record SaveAction()
PUT /Controller
- update multiple records SaveMultiAction()
POST/PUT /Controller/ID
- update record SaveAction()
POST/DELETE /Controller/ID
- delete record (POST body should be empty) DeleteAction()
GET/POST /Controller/(Something)[/ID]
- call for arbitrary action from the controller SomethingAction()
For example GET /Products
will call ProductsController.IndexAction()
And this will cause rendering templates from /www/template/products/index
highlighted as bold is where you could place your code.
FW.run()
FwHooks.initRequest()
- place code here which need to be run on request startfw.dispatch()
- performs REST urls matching and calls controller/action, if no controller found calls HomeController.NotFoundAction()
, if no requested action found in controller - calls controller action defined in contoller's route_default_action
(either "index" or "show")
fw._auth()
- check if user can access requested controller/action, also performs basic CSRF validationfw.call_controller()
SomeController.init()
- place code here which need to be run every time request comes to this controllerSomeController.SomeAction()
- your code for particular action
SomeModel.someMethod()
- controllers may call model's methods, place most of your business logic in modelsfw.Finalize()
GET /Admin/Users
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.IndexAction()
/template/admin/users/index/
GET /Admin/Users/123/edit
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.ShowFormAction(123)
Users.one(123)
/template/admin/users/showform/
POST /Admin/Users/123
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.SaveAction(123)
Users.update(123)
fw.redirect("/Admin/Users/123/edit")
//redirect back to edit screen after db updatedGET /Admin/Users/(Custom)/123?param1=1¶m2=ABC - controller's custom action (non-standard REST)
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.CustomAction(123)
- here you can get params using reqi("param1") -> 1
and reqs("params") -> "ABC"
/template/admin/users/custom/
unless you redirect somewhere elsePOST /Admin/Users/(Custom)/123 with posted params param1=1
and param2=ABC
FwHooks.initRequest()
AdminUsers.init()
AdminUsers.CustomAction(123)
- here you can still get params using reqi("param1") -> 1
and reqs("params") -> "ABC"
/template/admin/users/custom/
unless you redirect somewhere elseFrequently asked details about flow for the IndexAction()
(in controllers inherited from FwAdminController
and FwDynamicController
):
initFilter()
- initializes this.list_filter
from query string filter params &f[xxx]=...
, note, filters remembered in sessionsetListSorting()
- initializes this.list_orderby
based on list_filter("sortby")
and list_filter("sortdir")
, also uses this.list_sortdef
and this.list_sortmap
which can be set in controller's init()
or in config.json
setListSearch()
- initializes this.list_where
based on list_filter("s")
and this.search_fields
setListSearchStatus()
- add to this.list_where
filtering by status
field if such field defined in the controller's modelgetListRows()
- query database and save rows to this.list_rows
(only current page based on this.list_filter("pagenum")
and this.list_filter("pagesize")
). Also sets this.list_count
to total rows matched by filters and this.list_pager
for pagination if there are more than one page. Uses this.list_view
, this.list_where
, this.list_orderby
You could either override these particular methods or whole IndexAction()
in your specific controller.
The following controller fields used above can be defined in controller's init()
or in config.json
:
this.list_sortdef
- default list sorting in format: "sort_name[ asc|desc]"this.list_sortmap
- mapping for sort names (from list_filter["sortby"]
) to actual db fields, Hashtable sort_name => db_field_name
this.search_fields
- search fields, space-separatedthis.list_view
- table/view to use in getListRows()
, if empty model's table_name
usedApplication configuration available via fw.config->[SettingName]
.
Most of the global settings defined in config.*.php
. But there are several caclulated settings:
SettingName | Description | Example |
---|---|---|
hostname | set from server variable HTTP_HOST | osalabs.com |
ROOT_DOMAIN | protocol+hostname | https://osalabs.com |
ROOT_URL | part of the url if Application installed under sub-url | /suburl if App installed under osalabs.com/suburl |
site_root | physical application path to the root of public directory | C:\inetpub\somesite\www |
template | physical path to the root of templates directory | C:\inetpub\somesite\www\template |
log | physical path to application log file | C:\inetpub\somesite\logs\osafw.log |
tmp | physical path to the system tmp directory | C:\Windows\Temp |
In FwDynamicController
controller behaviour defined by /template/CONTROLLER/config.json
. Sample file can be fount at /template/admin/demosdynamic/config.json
This config file allows to define/override several properties of the FwController
(for example: as model
, save_fields
, search_fields
, list_view
,...) as well as define configuration of Show (show_fields
) and ShowForm (showform_fields
) screens. Note is_dynamic_show
and is_dynamic_showform
should be set to true accordingly.
There are samples for the one show_fields
or showform_fields
element:
//minimal setup to display the field value
{
"type": "plaintext",
"field": "iname",
"label": "Title"
},
Renders:
<div class="form-row">
<label class="col-form-label">Title</label>
<div class="col">
<p class="form-control-plaintext">FIELD_VALUE</p>
</div>
</div>
//more complex - displays dropdown with values from lookup model
{
"type": "select",
"field": "demo_dicts_id",
"label": "DemoDicts",
"lookup_model": "DemoDicts",
"is_option0": true,
"class_contents": "col-md-3",
"class_control": "on-refresh"
},
Renders:
<div class="form-row">
<label class="col-form-label">DemoDicts</label>
<div class="col-md-3">
<select id="demo_dicts_id" name="item[demo_dicts_id]" class="form-control on-refresh">
<option value="0">- select -</option>
... select options from lookup here...
</select>
</div>
</div>
Field name | Description | Example |
---|---|---|
type | required, Element type, see values in table below | select - renders as <select> html |
field | Field name from database.table or arbitrary name for non-db block | demo_dicts_id - in case of select id value won't be displayed, but used to select active list element |
label | Label text | Demo Dictionary |
lookup_model | Model name where to read lookup values | DemoDicts |
is_option0 | only for "select" type, if true - includes <option value="0">option0_title</option> |
false(default),true |
is_option_empty | only for "select" type, if true - includes <option value="">option0_title</option> |
false(default),true |
option0_title | only for "select" type for is_option0 or is_option_empty option title | "- select -"(default) |
required | make field required (both client and server-side validation), for showform_fields only |
false(default),true |
maxlength | set input's maxlength attribute, for showform_fields only |
10 |
max | set input type="number" max attribute, for showform_fields only |
999 |
min | set input type="number" min attribute, for showform_fields only |
0 |
step | set input type="number" step attribute, for showform_fields only |
0.1 |
placeholder | set input's maxlength attribute, for showform_fields only |
"Enter value here" |
autocomplete_url | type="autocomplete". Input will get data from autocomplete_url?q=%QUERY where %QUERY will be replaced with input value, for showform_fields only |
/Admin/SomeLookup/(Autocompete) |
is_inline | type radio or yesno . If true - place all options in one line, for showform_fields only |
true(default),false |
rows | set textarea rows attribute, for showform_fields only |
5 |
class | Class(es) added to the wrapping div.form-row |
mb-2 - add bottom margin under the control block |
attrs | Arbitrary html attributes for the wrapping div.form-row |
data-something="123" |
class_label | Class(es) added to the label.col-form-label |
col-md-3(default) - set label width |
class_contents | Class(es) added to the div that wraps input control |
col(default) - set control width |
class_control | Class(es) added to the input control to change appearance/behaviour | "on-refresh" - forms refreshes(re-submits) when input changed |
attrs_control | Arbitrary html attributes for the input control | data-something="123" |
help_text | Help text displayed as muted text under control block | "Minimum 8 letters and digits required" |
admin_url | For type="plaintext_link", controller url, final URL will be: "<~admin_url>/<~lookup_id>" | /Admin/SomeController |
lookup_id | to use with admin_url, if link to specific ID required | 123 |
att_category | For type="att_edit", att category new upload will be related to | "general"(default) |
validate | Simple validation codes: exists, isemail, isphone, isdate, isfloat | "exists isemail" - input value validated if such value already exists, validate if value is an email |
Type | Description |
---|---|
_available for both show_fields and showformfields | |
plaintext | Plain text |
plaintext_link | Plain text with a link to "admin_url" |
markdown | Markdown text (server-side rendered) |
noescape | Value without htmlescape |
float | Value formatted with 2 decimal digits |
checkbox | Read-only checkbox (checked if value equal to true value) |
date | Date in default format - M/d/yyyy |
date_long | Date in logn forma - M/d/yyyy hh:mm:ss |
multi | Multi-selection list with checkboxes (read-only) |
att | Block for displaying one attachment/file |
att_links | Block for displaying multiple attachments/files |
added | Added on date/user block |
updated | Updated on date/user block |
_available only showformfields | |
group_id | ID with Submit/Cancel buttons block |
group_id_addnew | ID with Submit/Submit and Add New/Cancel buttons block |
select | select with options html block |
input | input type="text" html block |
textarea | textaread html block |
input type="email" html block | |
number | input type="number" html block |
autocomplete | input type="text" with autocomplete using "autocomplete_url" |
multicb | Multi-selection list with checkboxes |
radio | radio options block |
yesno | radio options block with Yes(1)/No(2) only |
cb | single checkbox block |
date_popup | date selection input with popup calendar block |
att_edit | Block for selection/upload one attachment/file |
att_links_edit | Block for selection/upload multiple attachments/files |
Main and recommended approach - use logger()
function, which is globally available.
Examples: logger("some string to log", var_to_dump)
, logger("WARN", "warning message")
All logged messages and var content (complex objects will be dumped wit structure when possible) written on debug console as well as to log file (default /logs/osafw.log
)
You could configure log level in your config.*.php
- search "LOG_LEVEL"
Another debug functions that might be helpful are:
rw($var)
this function will work like var_dump and just dump variable structure and data to browser (with some formatting)rwe($var)
same as above, but immediately die to stop scriptuser_lists
(lowercase, underscore delimiters is optional)UserLists
(UpperCamelCase)UserListsController
or AdminUserListsController
(UpperCamelCase with "Controller" suffix)/template/userlists
/db/fwdatabase.sql
- this file is used to create db from scratch/db/updates/updYYYY-MM-DD[-123].sql
with all the CREATE, ALTER, UPDATE... - this will allow to apply just this update to existing database instancesfw.route_redirect()
if you got request to one Controller.Action, but need to continue processing in another Controller.Action
IndexAction()
just use fw.routeRedirect("ShowForm")
/www/upload
(default, see "UPLOAD_DIR" in config.*.php
)/upload
S3
model and upload to the cloudValidate()
. See usage example in AdminDemosController
logger()
and review /logs/osafw.log
if you stuck
config.*.php
AdminReportsController
IndexAction
- shows a list of all available reports (basically renders static html template with a link to specific reports)ShowAction
- based on passed report code calls related Report modelFwReports
, major methods (you may override in the specific report):
getReportFilters()
- set data for the report filtersgetReportData()
- returns report data, usually based on some sql query (see Sample report)ReportSample
model (in \www\php\models\Reports
folder) is a sample report implementation, that can be used as a template to build custom reports\www\php\models\Reports\Sample.php
to \www\php\models\Reports\Cool.php
(to create Cool report)Cool.php
and rename "Sample" to "Cool"getReportFilters()
to match your report filtersgetReportData()
to edit sql query and related post-processing\www\template\reports\sample
to \www\template\reports\cool
title.html
- report titlelist_filter.html
- for filtersreport_html.html
- for report table/layout/appearance\www\template\reports\index\main.html