|
|
|
|
===============
|
|
|
|
|
Creating Models
|
|
|
|
|
===============
|
|
|
|
|
|
|
|
|
|
A model represents the data which the application will use and takes
|
|
|
|
|
care of the interaction to that data. In OPNsense most of the relevant
|
|
|
|
|
data is physically stored in an xml structure (config.xml). The primary
|
|
|
|
|
goal for OPNsense models is to structure the use of configuration data,
|
|
|
|
|
by creating a clear abstraction layer.
|
|
|
|
|
|
|
|
|
|
In this chapter we will explain how models are designed and build.
|
|
|
|
|
|
|
|
|
|
-------------------
|
|
|
|
|
Designing the model
|
|
|
|
|
-------------------
|
|
|
|
|
|
|
|
|
|
Creating models for OPNsense is divided into two separate blocks:
|
|
|
|
|
|
|
|
|
|
#. A php class describing the actions on our data (also acts as a
|
|
|
|
|
wrapper to our data),
|
|
|
|
|
#. The definition of the data and the rules it should apply to.
|
|
|
|
|
|
|
|
|
|
Every model's class should be derived from OPNsense\\Base\\BaseModel, a very
|
|
|
|
|
simple model without any (additional) logic is defined with:
|
|
|
|
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
namespace myVendorName\myModule;
|
|
|
|
|
|
|
|
|
|
use OPNsense\Base\BaseModel;
|
|
|
|
|
|
|
|
|
|
class myModel extends BaseModel
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
This class should be placed inside the model directory of our project, in this
|
|
|
|
|
case the full path for our class file would be
|
|
|
|
|
|
|
|
|
|
- /usr/local/opnsense/mvc/app/models/**myVendorName/myModule/myModel.php**
|
|
|
|
|
|
|
|
|
|
When you design a model, the next thing to do is to figure out what data is
|
|
|
|
|
relevant for your application or module and think of the rules it should comply
|
|
|
|
|
to (for example, if you need an email address you might want to validate the
|
|
|
|
|
input). Designing the actual model is as simple as creating an xml file and
|
|
|
|
|
putting in your structure, the name of our xml file should be the same as the
|
|
|
|
|
base name of our model suffixed by .xml.
|
|
|
|
|
|
|
|
|
|
Using the same model, we would create the following file:
|
|
|
|
|
|
|
|
|
|
- /usr/local/opnsense/mvc/app/models/**myVendorName/myModule/myModel.xml**
|
|
|
|
|
|
|
|
|
|
And start describing our (information) model, like this:
|
|
|
|
|
|
|
|
|
|
.. code-block:: xml
|
|
|
|
|
|
|
|
|
|
<model>
|
|
|
|
|
<mount>//myManufacturer/myModule</mount>
|
|
|
|
|
<description>A description of this model (metadata)</description>
|
|
|
|
|
<items>
|
|
|
|
|
<exampleNumber type="TextField">
|
|
|
|
|
<Mask>/^([0-9]){0,1}$/</Mask>
|
|
|
|
|
<Default>5</Default>
|
|
|
|
|
<ValidationMessage>you should input a number from 0 to 9</ValidationMessage>
|
|
|
|
|
</exampleNumber>
|
|
|
|
|
<contacts>
|
|
|
|
|
<entity type="ArrayField">
|
|
|
|
|
<email type="EmailField">
|
|
|
|
|
<ValidationMessage>you should input a valid email address!</ValidationMessage>
|
|
|
|
|
</email>
|
|
|
|
|
<name type="TextField"/>
|
|
|
|
|
</entity>
|
|
|
|
|
<someText type="TextField"/>
|
|
|
|
|
</contacts>
|
|
|
|
|
</items>
|
|
|
|
|
</model>
|
|
|
|
|
|
|
|
|
|
Now let's explain what's happing here one tag at a time.
|
|
|
|
|
|
|
|
|
|
#. the <model> tag is used for identification of the file. (this is a
|
|
|
|
|
model file)
|
|
|
|
|
#. Next in line is the <mount> tag, this tells the system where this
|
|
|
|
|
information lives in the configuration file, in this case
|
|
|
|
|
ROOT\_tag/myManufacturer/myModule
|
|
|
|
|
#. If desired, there is some space reserved to explain the usage of the
|
|
|
|
|
model, the <description> tag
|
|
|
|
|
#. Last item on the top of our list is the <items> tag, this is where
|
|
|
|
|
the magic begins.
|
|
|
|
|
|
|
|
|
|
The content of a items tag describes the full tree based structure which holds
|
|
|
|
|
our data, in theory this could be as large as you want it to be, but keep in
|
|
|
|
|
mind that the content for your model should be logical and understandable. Every
|
|
|
|
|
node in the tree could have a type, which defines it's behavior, nodes without a
|
|
|
|
|
type are just containers.
|
|
|
|
|
|
|
|
|
|
From top to bottom we find the following nodes in our tree:
|
|
|
|
|
|
|
|
|
|
- exampleNumber, defined as a TextField
|
|
|
|
|
|
|
|
|
|
- Mask, validation can be performed by a regex expression, this sets
|
|
|
|
|
the expression
|
|
|
|
|
- Default, this field is default filled with a number: 5
|
|
|
|
|
- ValidationMessage, when validation fails, this message is returned
|
|
|
|
|
|
|
|
|
|
- contacts, this is a container
|
|
|
|
|
- entity, is defined as a recurring item, which holds the next items
|
|
|
|
|
- email, defined as an EmailField
|
|
|
|
|
|
|
|
|
|
- when validation fails, the **ValidationMessage** is returned
|
|
|
|
|
|
|
|
|
|
- name, defined as a TextField without any validations
|
|
|
|
|
- someText, not part of the entity tag and also defined as text without
|
|
|
|
|
validation
|
|
|
|
|
|
|
|
|
|
The fieldtypes are easily extendable in the base system and they all live in
|
|
|
|
|
their own namespace at *OPNsense\\Base\\FieldTypes* deriving from *BaseField*.
|
|
|
|
|
|
|
|
|
|
-------------
|
|
|
|
|
Usage example
|
|
|
|
|
-------------
|
|
|
|
|
|
|
|
|
|
Now let's test our model using a small php script (in /usr/local/opnsense/mvc/script/ ):
|
|
|
|
|
|
|
|
|
|
.. code-block:: php
|
|
|
|
|
|
|
|
|
|
<?php
|
|
|
|
|
// initialize phalcon components for our script
|
|
|
|
|
require_once("load_phalcon.php");
|
|
|
|
|
|
|
|
|
|
// include myModel and the shared config component
|
|
|
|
|
use myVendorName\myModule\myModel;
|
|
|
|
|
use OPNsense\Core\Config;
|
|
|
|
|
|
|
|
|
|
// create a new model, reading the model definition and the current data from our config.xml
|
|
|
|
|
$myMdl = new myModel();
|
|
|
|
|
$myMdl->exampleNumber =1;
|
|
|
|
|
$myMdl->contacts->someText = "just a test";
|
|
|
|
|
|
|
|
|
|
// add a new contact node
|
|
|
|
|
$node = $myMdl->contacts->entity->add();
|
|
|
|
|
$node->email = "test@test.com";
|
|
|
|
|
$node->name = "my test user";
|
|
|
|
|
|
|
|
|
|
// perform validation on the data in our model
|
|
|
|
|
$validationMessages = $myMdl->performValidation();
|
|
|
|
|
foreach ($validationMessages as $messsage) {
|
|
|
|
|
echo "validation failure on field ". $messsage->getField()." returning message : ". $messsage->getMessage()."\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if validation succeeded, write data back to config
|
|
|
|
|
if ($validationMessages->count() == 0) {
|
|
|
|
|
// serialize our model to the config file (config.xml)
|
|
|
|
|
// (this raises an error on validation failures)
|
|
|
|
|
$myMdl->serializeToConfig();
|
|
|
|
|
$cnf = Config::getInstance();
|
|
|
|
|
$cnf->save();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If you fill in an invalid value to one of the validated fields, you can easily
|
|
|
|
|
try the validation. Try to input the text "X" into the field exampleNumber to try out.
|
|
|
|
|
|
|
|
|
|
When inspecting our config.xml file, you will notice the following content has
|
|
|
|
|
been added to the root:
|
|
|
|
|
|
|
|
|
|
.. code-block:: xml
|
|
|
|
|
|
|
|
|
|
<myManufacturer>
|
|
|
|
|
<myModule>
|
|
|
|
|
<exampleNumber>1</exampleNumber>
|
|
|
|
|
<contacts>
|
|
|
|
|
<entity>
|
|
|
|
|
<email>test@test.com</email>
|
|
|
|
|
<name>my test user</name>
|
|
|
|
|
</entity>
|
|
|
|
|
<someText>just a test</someText>
|
|
|
|
|
</contacts>
|
|
|
|
|
</myModule>
|
|
|
|
|
</myManufacturer>
|
|
|
|
|
|
|
|
|
|
----------
|
|
|
|
|
Guidelines
|
|
|
|
|
----------
|
|
|
|
|
|
|
|
|
|
.. rubric:: Some (simple) guidelines developing models
|
|
|
|
|
:name: some-simple-guidelines-developing-models
|
|
|
|
|
|
|
|
|
|
#. One model should always be completely responsible for the its mount
|
|
|
|
|
point, so if there's a model at mount point /A/B there can't be a
|
|
|
|
|
model at /A/B/C
|
|
|
|
|
#. Try to keep models logical and understandable, it's better to build
|
|
|
|
|
two models for you application if the content of two parts aren't
|
|
|
|
|
related to each other. It's no issue to create models at deeper
|
|
|
|
|
levels of the structure.
|
|
|
|
|
|
|
|
|
|
#. When using more models in a application/module, you might want to
|
|
|
|
|
consider the following naming convention: /Vendor/Module/Model
|
|
|
|
|
|
|
|
|
|
#. Try to avoid more disc i/o actions than necessary, only call save()
|
|
|
|
|
if you actually want to save content, serializeToConfig just keeps
|
|
|
|
|
the data in memory.
|