Defining models for CRUD pages
The core of BPFW are the models, views and controls that represent a specific database table.
You can easily insert many predefined or your own components to manage the data the way you need it.
Models are the core of every BPFW page and since they contain essential information, they are required for every page.
To add a model, create a file inside your apps/(projectname)/mvc/models with the name (modelname)Model.inc.php that extends BpfwModel.
As an absolute minimum, you have to implement the abstract methods loadDbModel and GetTitle:
This might look like that:
class ExamplesimpleModel extends BpfwModel
{
/**
* @return array
* @throws Exception
*/
protected function loadDbModel(): array
{
$this->addPrimaryKey("examplesimpleId");
$this->addComboBox("combobox", "Combobox", array(1=>"first val", 2=>"second val"));
$this->addTextField("test", "Text");
$this->addTextFieldNumeric("number", "Number");
$this->addCheckbox("checkbox", "Checkbox");
$this->addTinyMceHtmlEditor("tinymce", "Tinymce Editor", array(FORMSETTING::POSITION => POSITION_RIGHT, "hiddenOnList" => true));
return $this->dbModel;
}
public function GetTitle(): string
{
return "Simple Example found in examplesimpleModel.inc.php";
}
}
Now go to the Administrator -> dbSync page to create the table “examplesimple” inside your database. That’s pretty much it for a basic CRUD page that might look like this and is fully usable:
About the advanced optional see below:
GetTableName
If you want a tablename that is not the name of the model, you can define it here:
/**
* Defines the tablename
* @return string
*/
public function GetTableName(): string
{
return "examplesimple";
}
EditEntry, AddEntry, GetEntry, GetEntries, DeleteEntry
You might want to manipulate the results before they are sent to the database (AddEntry, EditEntry, DeleteEntry), when displayed in the form (GetEntry) or the List(GetEntries).
This can be done by overwriting the methods from core/db/bpfwModel.inc.php
/**
* @param array $data
* @param bool $ignoreRightsManagement
* @param bool $ignoreConversion
* @param bool $temptable
* @return void
* @throws Exception
*/
public function EditEntry(array $data, bool $ignoreRightsManagement = false, bool $ignoreConversion = false, bool $temptable = false): void
{
// manipulate submitvalues here
parent::EditEntry($data, $ignoreRightsManagement, $ignoreConversion, $temptable);
}
/**
* @param array|DbSubmitValue[] $data keyvalue or data array or DbModelEntry or array of values
* @throws Exception
*/
public function AddEntry(array $data, bool $ignoreRightsManagement = false, bool $ignoreConversion = false, bool $temptable = false): ?int
{
// manipulate submitvalues here
return parent::AddEntry($data, $ignoreRightsManagement, $ignoreConversion, $temptable);
}
/**
* Manipulate display results in form
* @param int|string|null $key
* @param string $where
* @param int $count
* @param int $offset
* @param string $join
* @param bool $temptable
* @param bool $disablePermissionCheck
* @return DbModelEntry|null
* @throws Exception
*/
public function GetEntry(int|string|null $key, string $where = " 1", int $count = -1, int $offset = 0, string $join = "", bool $temptable = false, bool $disablePermissionCheck = false): ?DbModelEntry
{
$data = parent::GetEntry($key, $where, $count, $offset, $join, $temptable, $disablePermissionCheck);
if (!is_numeric($data->test_numeric_1) || !is_numeric($data->test_numeric_2)) {
$data->test_calculate = " - ";
} else {
$data->test_calculate = $data->test_numeric_1 + $data->test_numeric_2;
}
return $data;
}
/**
* Manipulate display results in form
* @param string $where
* @param int $count
* @param int $offset
* @param array $sort
* @param string $join
* @param bool $temptable
* @return array|DbModelEntry[]
* @throws Exception
*/
public function GetEntries(string $where = " 1", int $count = -1, int $offset = 0, array $sort = array(), string $join = "", bool $temptable = false): array
{
$allDataOfPage = parent::GetEntries($where, $count, $offset, $sort, $join, $temptable);
foreach ($allDataOfPage as $key => $data) {
if (!is_numeric($data->test_numeric_1) || !is_numeric($data->test_numeric_2)) {
$data->test_calculate = " - ";
} else {
$data->test_calculate = $data->test_numeric_1 + $data->test_numeric_2;
}
}
return $allDataOfPage;
}
GetTabName
If your Form has defined multiple Pages, you can change their Names here:
function getTabName($editMode, $pageID): string
{
if ($pageID == 1) return "Basic";
if ($pageID == 2) return "Textareas";
if ($pageID == 3) return "Subtables";
return parent::getTabName($editMode, $pageID);
}
will result in:
Constraints
You can define constraints in your model to prevent deletion or automatically delete all sub entries. The constraints have to be in the parent model.
There is
FK_CONSTRAINT_CACADE -> delete child entries
FK_CONSTRAINT_NULL -> set parent link in child entries to null
FK_CONSTRAINT_RESTRICT -> show a message box saying “you cant delete that because …”
The constraints are implemented in the PHP code and not stored in the database.
/**
* Define your constraints here. They have to be in the parent model. So if your uses has addresses, define in user etc.
* @return DatabaseFKConstraint[]
*/
public function getConstraints(): array
{
return array(
// FK_CONTRAINT_RESTRICT: show an error dialog if you want to delete an entry that still has uploaded attachments
new DatabaseFKConstraint("preventdeleteifstillattachments", "exampleattachments", "exampleattachmentsId", $this->getKeyName(), FK_CONTRAINT_RESTRICT),
// FK_CONTRAINT_CASCADE: delete entries of submodel database when deleted
new DatabaseFKConstraint("deleteSubIfParentDeleted", "examplecomplexsub", "examplecomplexsubId", $this->getKeyName(), FK_CONTRAINT_CASCADE),
);
}
Nested Forms
You can use Nested forms that can for example contain items of an order oder addresses of an user.
To use forms inside another form and only display their data, you have to do a few things:
1. Create another form model php file (here: Examplecomplexsubmodel.inc.php) that contains the key of the parent inside as a hidden field (examplecomplexId) and set filteredField, filteredModel and a member variable with the filter:
class ExamplecomplexsubModel extends BpfwModel
{
// enable if you want all the labels in loadDbModel to be translated
// var bool $translateDbModelLabels = true;
// stores the current filter for the entries
// can also be "formid" to use value of parent form, so has to be string
private $examplecomplexId;
function __construct($examplecomplexId = null){
$this->examplecomplexId = $examplecomplexId;
// autoadds a FILTER_ENTRY_SELECT_WHERE filter
$this->filteredField = "examplecomplexId";
$this->filteredModel = "examplecomplex";
$filter = getorpost("filter");
if(!empty($filter) && $this->examplecomplexId === null){
$this->examplecomplexId = getorpost("filter");
}
parent::__construct();
}
/**
* tablename
* @return string
*/
public function GetTableName(): string
{
return "examplecomplexsub";
}
public function GetTitle(): string
{
return "Submodel of examplecomplex";
}
/**
* @return array
* @throws Exception
*/
protected function loadDbModel(): array
{
$this->addPrimaryKey("examplecomplexsubId");
// this will automatically add the Filtervalue of the parent to the database
$this->addHiddenField("examplecomplexId", "examplecomplexId", BpfwDbFieldType::TYPE_INT, $this->examplecomplexId, array());
$this->addTextField("firstname", "Firstname");
$this->addTextField("lastname", "Lastname");
$this->addTextFieldNumeric("anothervalue", "Another value");
return $this->dbModel;
}
}
Now create a “modellistfiltered” component inside the parent component:
Parameter:
1: Name
2: Label
3: Name of the model used
4: Fields you want to show in the listview
5: Optional parameters
protected function loadDbModel(): array
{
$this->addPrimaryKey("examplecomplexId");
$this->addModellistFiltered("test_modellistfiltered", "test_modellistfiltered", "examplecomplexsub", array( "firstname", "lastname", "anothervalue" ), array(LISTSETTING::AJAX_SORTABLE=>false, LISTSETTING::LABEL_LIST=>"test modelllist filtered", FORMSETTING::PAGE=>3, LISTSETTING::HIDDENONLIST=> true));
}
As a last step you might want to add constraints to avoid trash inside your database.
You probably want to use FK_CONTRAINT_CASCADE (autodelete subentries) or FK_CONTRAINT_RESTRICT (show error if there are still entries):
/**
* Define your constraints here. They have to be in the parent model. So if your uses has addresses, define in user etc.
* @return DatabaseFKConstraint[]
*/
public function getConstraints(): array
{
return array(
// FK_CONTRAINT_CASCADE: delete entries of submodel database when deleted
new DatabaseFKConstraint("deleteSubIfParentDeleted", "examplecomplexsub", "examplecomplexsubId", $this->getKeyName(), FK_CONTRAINT_CASCADE),
);
}
Hint: there is also a Modellist to show unfiltered entries of another table. Using it is a lot more simple, just add it to your parent component as shown in step 2 using addModellist(); instead of addModellistFiltered()
Rights Management / Limit the access to a model
You can define the permission to view, edit, delete and duplicate in the constructor.
Per default all of those are set du USERTYPE_ADMIN.
Every rank has a number starting with developer as 0, admin as 5 and so on. Allowing access to some rank also includes the ranks with a lower rank number. So giving access to a moderator with ID 10 also includes users with the superadmin(0) and admin rank(5).
function __construct()
{
parent::__construct();
// rights management with prededefined roles
$this->minUserrankForEdit = USERTYPE_ADMIN;
$this->minUserrankForShow = USERTYPE_CONSULTANT;
$this->minUserrankForDelete = USERTYPE_SUPERADMIN;
$this->minUserrankForAdd = USERTYPE_ADMIN;
$this->minUserrankForDuplicate = USERTYPE_INVALID;
}
Default sorting
You can define the default sorting order in the constructor:
function __construct()
{
parent::__construct();
// define which column and order is default sort
// column 2 is the first "real" column, 1 is the invisible id field
$this->sortColumn = 2;
$this->sortOrder = "desc";
}