Overview

In an ActiveWAFL application, the bottom tier is represented by one or more Data Models. Data Models with an underlying data Storage Engine can be persisted directly to (and deleted from) the Storage Engine.

Data Models should never be directly instantiated and/or operated on from the middle or top tiers. The middle-tier can interact with the Functional Models, which are a higher-level representations of the data.
Every Data Model has a corresponding Functional Model.

Connect to a database

ActiveWafl uses DataStorage drivers to connect to databases.

Settings.dev.syrp sample
  1. DataStorage
  2.    Common
  3.       LoadBalanced   =  0
  4.  
  5.    Master
  6.       Uri                 =   192.168.1.20 //mysql hostname or ip address
  7.       Catalog             =   your-db-name
  8.       Username            =   your-db-username
  9.       Password            =   your-db-password
  10.       RequiredLocation    =   some-table-that-should-always-exist
  11.       CreateScript        =   Database/Sql/CreateDatabase.sql
  12.       UpdateScript        =   Database/Sql/NextUpdate.sql
  13.       ReplicationRole     =   Master
  14.       PersistModels       =   {true}
  15.       ModelGroup          =   UsuallyTheSameAsYourApplicationNamespace
  16.       AccessScope         =   ReadWrite
  17.       EngineDriverPath    =   \\Wafl\\Extensions\\Storage\\Mysql
  18.       CharacterEncoding   =   utf8

Generate Model Classes

UpdateDataModel

In the application's Bin/ directory there is a utility, UpdateDataModel, that you can use to generate the Data Model and the Functional Model classes.
The Data Model classes are abstract Active Records.
The Functional Model classes inherit from the Data Model classes. Any logic that applies to the Data Models should be put into the Functional Model classes.

Output from the UpdateDataModel utility, from the WaflCones demonstration application. In this case, the Functional Model already existed, so only the Data Model is updated.

C:\xampp\vhosts\shared\CrashCourse\WaflCones.dev\WaflCones\Bin>UpdateDataModel

C:\xampp\vhosts\shared\CrashCourse\WaflCones.dev\WaflCones\Bin>call WaflCliBoots
trap.bat
ActiveWAFL CLI...
ActiveWAFL environment is set to c:/xampp/vhosts/shared/Wafl/
Updating the data model...

ActiveWAFL Utilities

0.3.x - UpdateDataModel Utility
App Initialized
Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Fu
nctionalModel/Employee.php...
Skipped Functional Model because it already exists

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Da
taModel/Employee.php...
Done (Overwrote Data Model)

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Da
taModel/Employee.js...
Done (Overwrote Data Model)

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Fu
nctionalModel/Employee.js...
Skipped Functional Model because it already exists

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Fu
nctionalModel/Flavor.php...
Skipped Functional Model because it already exists

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Da
taModel/Flavor.php...
Done (Overwrote Data Model)

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Da
taModel/Flavor.js...
Done (Overwrote Data Model)

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Fu
nctionalModel/Flavor.js...
Skipped Functional Model because it already exists

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Fu
nctionalModel/SaleItem.php...
Skipped Functional Model because it already exists

Saving file c:/xampp/vhosts/shared/CrashCourse/WaflCones.dev/WaflCones\Models/Da
taModel/SaleItem.php...
Done (Overwrote Data Model)

C:\xampp\vhosts\shared\CrashCourse\WaflCones.dev\WaflCones\Bin>
 

Functional Models

Functional Models can be instanstiated from your application's logic either by constructing them directly or using one of the available helper methods for searching or querying.

All Functional Models implement IFieldAggregatorUtil, IFilterable, and IIndexable.
If the model is connected to an underlying Storage Engine (ie MySql) and/or Search Index (ie Apache Solr) then those interfaces can be used to find and load your Functional Models.

Load Functional Models

IFilterable provides static methods for getting data from a Storage Engine (such as MySql).

Usage
  1. $arrayOfObjects = FunctionalModelName::Filter($filter, $orderByFieldName, $maxRecordCount, $groupingField, $joinObjects, $startOffset, $arrayKeyField);
Example: loading 20 employees from a database
  1. //get first 20 employees with last name that starts with A sorted by their LastName and then by their FirstName
  2. use \MyCompanyTool\FunctionalModel\Employee;
  3. $employees = Employee::Filter("LastName like 'A%'","LastName, FirstName",20);

To load an individual record from your Storage Engine, instantiate the Functional Model class with the record's primary key as the first argument.

Usage
  1. $object = new MyModelClassName($myModelPrimaryKeyValue);
Example controller action; loading an employee from a database based on passed in employee id
  1. $employee = new Employee($request->GetInputInteger("EmployeeId"));

You can also load an individual model using other criteria.

Usage
  1. $model = ModelClassName::FilterFirst($filter=null, $orderByFieldName=null, $groupingField=null, $joinObjects=null);
Example: Get an employee based on some criteria
  1. //get the first employee with last name that starts with A
  2. $employee = Employee::FilterFirst("LastName like 'A%'","LastName");

Create / Update

Once you instantiate a Functional Model, it's underlying Data Model can be persisted to it's underlying Storage Engine.
Create a new Functional Model
  1. $object = new MyModelClassName();
Update a Functional Model
  1. $object->Set_SomeProperty($someValue);
  2. $object->Set_SomeOtherProperty($someValue);
Persist a Functional Model
  1. $object->Save();

Example

Create a new Employee, update it, and save it to the database
  1. $employee = new Employee();
  2. $employee->Set_FirstName($firstName);
  3. $employee->Set_LastName($lastName);
  4. $employee->Save();

Sorting / Grouping

Order By Field(s)
IFilterable::Filter
  1. $arrayOfObjects = MyModelClassName::Filter($filter,$orderByFieldName,...);
Get all employees from MySql table sorted descending by their LastName
  1. $employees = Employee::Filter(null,"LastName desc");
The syntax for the $filter and $orderByFieldName arguments are determined by the Storage Engine being used.   Most Storage Engine drivers support SQL syntax.
Group By Field(s)
IFilterable::Filter
  1. $arrayOfObjects = MyModelClassName::Filter($filter,$orderByFieldName,$maxRecordCount,$groupingField,...);
Get all employees from MySql table grouped by their LastName
  1. $employees = Employee::Select(null,null,null,"LastName");

Data Bounds

You can set the bounds for the data set using two IFilterable Filter* arguments: $maxRecordCount and $startOffset

IFilterable::Filter
  1. $arrayOfObjects = MyModelClassName::Filter($filter,$orderByFieldName,$maxRecordCount,$groupingField,$joinObjects,$startOffset,...);
Example Get 100 employees from MySql table
  1. $employees = Employee::Filter(null,null,100);            //get 100 employees from MySql table
  2. $employees = Employee::Filter(null,null,100,null,null,300); //get 100 employees from MySql table, starting with record 300

Aggregate Data

All Functional Models implement IFieldAggregatorUtil, which exposes static methods for retriving aggregate information from the model

IFieldAggregatorUtil::Average, Count, Sum, GetMaximum, GetMinimum
  1. $avg = MyModelClassName::Average($fieldName, $filter = null, $groupingField = null, $joinFieldDefinitions = null);
  2. $count = MyModelClassName::Count($filter = null, $groupingField = null, $joinFieldDefinitions = null);
  3. $sum = MyModelClassName::Sum($fieldName, $filter = null, $groupingField = null, $joinFieldDefinitions = null);
  4. $max = MyModelClassName::GetMaximum($fieldName, $filter = null, $groupingField = null, $joinFieldDefinitions = null);
  5. $min = MyModelClassName::GetMinimum($fieldName, $filter = null, $groupingField = null, $joinFieldDefinitions = null);
Example Get aggregated employee information from MySql table
  1. //Get the average employee's age in the company
  2. $avgAge = Employee::Average("Age");
  3.  
  4. //Get the sum of all of the employees' experience
  5. $totalExperience = Employee::Sum("YearsExperience");
  6.  
  7. //Get the number of employees in the company
  8. $employeeCount = Employee::Count("EmployeeId");

Table Joins

Functional Models use inner joins as a criteria method for finding models. However the joined Functional Models are not combined into the result. Functional Model static data retrieval methods always return instances of themselves, regardless of the joined tables.

More complex data retrieval can be achieved by using lower-level methods on the Data Storage engine (See "Data Access").
IFilterable::Filter
  1. $arrayOfObjects = MyModelClassName::Filter($filter,$orderByFieldName,$maxRecordCount,$groupingField,<b>$joinObjects</b>,$startOffset,$arrayKeyField);
In this example, the tables EmployeeDepartments and EmployeeDepartments would be inner-joined on the EmployeeId column
  1. $joinObjects=array("EmployeeDepartments"=>"EmployeeId", "EmployeeDistricts"=>"EmployeeId");
IFilterable::Filter example with table join
  1. //get all employees that are in the "Sales" department in the "Midwest" district
  2. $joinObjects=array("EmployeeDepartments"=>"EmployeeId", "EmployeeDistricts"=>"EmployeeId");
  3.  
  4. //You should normally not use a string as the primary Id like we are in this example.
  5. //This was done here only to demonstrate clearly what the criteria is.
  6. $filter = "EmployeeDepartments.DepartmentId = "Sales" and EmployeeDistricts.DistrictId = "Midwest";
  7.  
  8. //note: this will return <b>Employee</b> objects, not EmployeeDepartments or EmployeeDistricts.
  9. $employees = Employee::Filter($filter,null,null,null,$joinObjects);

Client Side Data Access

Every Functional Model can be loaded from the Storage Engine and manipulated on the client-side (js) just the same as the server-side (php).

Retrieve Data

LoadInstaceByKey
  1. var object = DblEj.Data.Model.LoadInstaceByKey(objectType,objectId);
Load employee #235 from the MySql Database
  1. var object = DblEj.Data.Model.LoadInstaceByKey("Employee",235);

Manipulate and Persist Data

Property Settters
  1. //Set some properties on the models (there will be a property for each column in the table)
  2. object.Set_Property(newValue);
  3. object.Set_OtherProperty(otherNewValue);
  4. object.SaveToServer(); //save the data in the MySql database
Load employee #235, update some properties, and save to the MySql database
  1. var employee = DblEj.Data.Model.LoadInstaceByKey("Employee",235);
  2. employee.Set_FirstName("John");
  3. employee.Set_LastName("Doe");
  4. employee.SaveToServer(); //save the data in the MySql database;
  5. alert("New employee saved, id:"+employee.Get_EmployeeId());
Create new employee, update some properties, and save to the MySql database
  1. var employee = new MyCompanyTool.FunctionalModel.Employee();
  2. employee.Set_FirstName("John");
  3. employee.Set_LastName("Doe");
  4. employee.SaveToServer(); //save the data in the MySql database;
  5. alert("New employee saved, id:"+employee.Get_EmployeeId()); //if using auto-id columns, gets the new id (else gets the inserted id)

Serialization / JSON

Every Functional Model will be automatically serialized/deserialized when being sent over AJAX in either direction. The same applies to most objects and arrays. Simple data types should be wrapped in an AjaxResult before being returned.

To serialize a Functional Model and return it in response to an Ajax Request, return the Functional Model from the corresponding registered API Handler.

Get an array of Employees and have them serialized and sent over Ajax (JSON over HTTP, actually)
Client-side code
  1. var newEmployees = _("GetAllNewEmployees");
Server-side API Handler
  1. public static method GetAllNewEmployees($args)
  2. {
  3.    $timeCutoff = time() - 86400;
  4.    $return Employee::Filter("HireDate > $timeCutoff");
  5. }