This package for the Woltlab Community Framwork aims to make form development easier. Out of the box, creating forms for the ACP/Backend in the WCF is cumbersome, verbose and requires a lot of boilerplate code.
This package is targetted at developers, not at forum owners/admins. It doesn't add any functionality on its own, it just provides tools to make development easier. You don't need to install this package, if you don't also use other packages that make use of this one.
At this point, it only supports ACP forms, frontend form support will be a subject for the future. If you find any bugs, have questions or suggestions for new features, feel free to start GitHub issues for them.
The plugin can also be found in WoltLab's official Plugin Store.
<?php
namespace wcf\acp\form;
use wcf\form\AbstractForm;
use wcf\util\StringUtil;
class MyExampleAddForm extends AbstractForm {
public $activeMenuItem = "wcf.acp.menu.link.example.add";
public $exampleID = 0;
public $title = '';
public $description = '';
public function readFormParameters() {
parent::readFormParameters();
if (isset($_POST['exampleID'])) $this->exampleID = intval($_POST['exampleID']);
if (isset($_POST['title'])) $this->title = StringUtil::trim($_POST['title']);
if (isset($_POST['description'])) $this->description = StringUtil::trim($_POST['description']);
}
public function validate() {
parent::validate();
if (empty($this->title)) {
throw new UserInputException('name');
}
if (empty($this->description)) {
throw new UserInputException('description');
}
}
public function save() {
parent::save();
$this->objectAction = new wcf\data\example\ExampleAction(array(), 'create', array(
'data' => array(
'exampleID' => $this->exampleID,
'title' => $this->title,
'description' => $this->description,
),
));
$this->objectAction->executeAction();
$this->saved();
WCF::getTPL()->assign(array(
'success' => true,
));
}
public function assignVariables() {
parent::assignVariables();
WCF::getTPL()->assign(array(
'action' => 'add',
'exampleID' => $this->exampleID,
'title' => $this->title,
'description' => $this->description,
));
}
}
As you can see, even this simple example contains a lot of boilerplate and repetition. We define a form that managed three attributes: exampleID
, title
and description
, yet we need 62 lines of code to do that. We manually need to read the form parameters, define our save action, assign variables for template rendering and potentially even more.
<?php
namespace wcf\acp\form;
use wcf\form\FormBuilder;
class MyExampleAddForm extends FormBuilder {
public $activeMenuItem = "wcf.acp.menu.link.example.add";
protected function getAttributes() {
return array(
'title' => 'string',
'description' => 'string',
);
}
protected function getObjectActionType() {
return 'wcf\data\example\ExampleAction';
}
}
This short example accomplishes the same thing, yet it only requires us to write 18 lines of code. That's a reduction of over 70%! I'd argue it's a lot more readable but undoubtedly it's not at all repetitive or unnecessarily verbose.
Create a class that inherits from wcf\form\FormBuilder
. FormBuilder contains two abstract methods that need to be implemented: getAttributes()
and getObjectActionType()
.
This is the main method you need to implement. You define which attributes your form has and can be filled out in the form. It will be used to determine which template variables to fill, which fields to use in your objectAction and how to validate your form attributes.
protected function getAttributes() {
return array(
'description' => array(
// The type to which the value should be converted to
'type' => 'string',
// Should this value be required or not?
'required' => true,
// Skip this value when saving the object to the database
'skip' => false,
// Validation rule to be applied to the value (see "Validation Rules")
'rule' => 'string',
),
);
}
Alternatively, you can just specify the type of your attribute. This will automatically set it to a required attribute and will add an isset
rule to it.
protected function getAttributes() {
return array(
'description' => 'string',
);
}
The Form Builder automatically adds an ID field to your template variables called primaryID
.
isset
(Default) Verifies that the value is present in the requestinteger
Verifies that the value is an integernumeric
Verifies that the value is numeric, but doesn't have to be an integerstring
Verifies that the value is a stringdigits:4
Verifies that the value contains exactly 3 digits (including decimal point, i.e.2.34
but not1.864
)digitsBetween:4,8
Verifies that the value contains between 4 and 8 digits (inclusively)email
Verifies that the value is a valid email addressurl
Verifies that the value is a valid urldate
Verifies that the value is in a valid date formatclass:className
Verifies that this is a valid ID for an object of the given classcustom:methodName
Add your own validation rule
You can write your validation rules if the provided ones do not suffice:
protected function getAttributes() {
return array(
'categoryID' => array(
'rule' => 'custom:validateCategoryID',
),
);
}
private function validateCategoryID($value) {
$category = new \wcf\data\category\Category($value);
return $category->categoryID;
}
As you can see, by adding a custom method to your class (validateCategoryID
in this case) you can write your own verification logic. Your method will automatically be called on validation and if it returns a falsy value, a UserInputException
will be called with the type custom
.
This is just a simple example and is equivalent to the rule class:\wcf\data\category\Category
.
To save your model, you need to create a class that inherits from wcf\data\AbstractDatabaseObjectAction
. The Form Builder uses your object action implementation to create and update your model. Therefore you need to specify the class and namespace for your action.
protected function getObjectActionType() {
return 'wcf\data\example\ExampleAction';
// Or, with PHP >=5.5, if you're not planning to support lower versions:
// return \wcf\data\example\ExampleAction::class;
}
Needs to be set when $requiresValidObject
is set to true. It should return a string containing the name of your model, so it can be instantiated for you on a request.
protected function getObjectTypeName() {
return 'wcf\data\example\Example';
}
protected $modelAction
(Default:'create'
) The action to perform on the object (e.g.'create'
or'update'
)protected $usePersonalSave
(Default:false
) If set totrue
, the Form Builder's implementation ofsave()
will not be executed. You have to add your own implementation (including asuper::save()
-call)protected $templateAction
(Default:'add'
) The value that the template variableaction
will be set toprotected $requiresValidObject
(Default:false
) Whether or not a request needs a valid object
In case your code requires more complicated logic that isn't included in the Form Builder yet, you can just overwrite these methods like so:
public function validate()
{
// This will also call FormBuilder's validation methods,
// so you don't need to do twice the amount of work, just your additional logic.
parent::validate();
// Your complicated logic goes here
}
There's no definitive roadmap yet, but I'm planning on adding the following features:
- An automated form template builder alongside the FormBuilder class (This will be the main priority for version 1.0)
- Possibility for relations/dependent classes (i.e. each
Example
must be linked to a validExampleCategory
); This has been implemented with the "class" validation rule
This work is licensed under the GPLv3 license. For further information, either read the full license text or for a quick reference take a look at tl;dr Legal.