User Tools

Site Tools


datarecord_class

Datarecord class

The Datarecord class is one of the core classes of Platform, as it allows you to create highly flexible objects that are easily stored in a database.

You can look for the Templateclass class which provides a blueprint for your own Datarecord class and can easily be altered to your own purpose. The Templateclass is meant as an example file, so you shouldn't subclass it, but just copy and rename it.

Quick start

The most basic Datarecord class looks like this:

class Templateclass extends Datarecord {
 
    protected static $database_table = 'DATABASE TABLE';
    protected static $delete_strategy = self::DELETE_STRATEGY_BLOCK;
    protected static $location = self::LOCATION_INSTANCE;
 
    protected static $depending_classes = [ ];
    protected static $referring_classes = [ ];
 
    protected static $structure = false;
    protected static $key_field = false;
    protected static $title_field = false;
 
    protected static function buildStructure() {
        static::addStructure([]);
        parent::buildStructure();
    }
}

From here there is three steps to get it up and running.

1. Database table

We must decide for the database table name, which is done by overwriting the $database_table variable.

protected static $database_table = 'my_database_table';

2. Object definition

Now we must define which fields our Datarecord object consists of. This is done by adding the appropriate types to the object by calling the addStructure function with an array of types and then calling buildStructure(). This is an example of a simple structure for holding an address book consisting of a name, a phone number and an email:

    protected static function buildStructure() {
        static::addStructure([
            new KeyType('contact_id'),
            new TextType('full_name', 'Full name', ['is_required' => true, 'is_title' => true]),
            new TextType('phone_number', 'Phone number'),
            new EmailType('email', 'Email')
        ]);
        parent::buildStructure();
    }

The first parameter is the desired database field, the next is the human readable field name and the last are specific options for the selected type.

3. Build table

To ensure the table is build in the database and ready for use, we need to call the ensureInDatabase() function of our new class, which will automatically build an appropriate SQL table. If you later extend or modify the structure of your class, just call this function again to modify the database to match your modifications.

The natural place to call this function is in the initializeDatabase() function in your subclass of Instance.

After this, your object is ready to use.

Basic object usage (read / write)

A Datarecord is created just like a regular object

$contact = new Contact();

The following two are equivalent and are used to assign a value to a field in the object:

$contact->setValue('full_name', 'Michael Sahl');
$contact->full_name = 'Michael Sahl';

These fields can be read back using one of the following (where both are equivalent):

$full_name = $contact->getRawValue('full_name');
$full_name = $contact->full_name;

Up until now we have only had the object in memory, but we can store it in the database by using the save() function:

$contact->save();

This will save the object in the database (assuming we have built the corresponding table using the ensureInDatabase() function) and also automatically fill the key field with the assigned ID from the database if this is the first time the object is saved.

We can retrieve the key:

echo $contact->contact_id;

If we need to retrieve this object at a later time, we can just use this ID in one of the following ways:

$contact = new Contact();
$contact->loadForRead($id);
 
// OR 
 
$contact = new Contact();
$contact->loadForWrite($id);

When loading objects from the database we can both load them in a read context and a write context.

Loading an object for read prevents you from saving it again, and should only be used if you are interested in retrieving information from the object without changing it.

Loading an object for write will lock the object, so no other process can change it, before we are finished with it. This is to ensure that two processes doesn't change the same object at the same time thereby having one process overwriting the changes from the other process.

All objects locked this can be unlocked in one of three ways:

  1. By saving it. The object will be switched back into read mode afterwards.
  2. By calling the unlock() function. This will switch the object back to read mode without saving changes.
  3. At the end of script execution.

Deleting objects

An object which have been saved to the database can be deleted by opening it for write and calling the delete() function:

$contact->delete();

More about values

Values in the Datarecord objects can accessed in different ways. Up until now we have only accessed the raw values, which are the programmatically or technical values of the field. But there are several other ways to access field values. For example we could imagine a telephone number. The raw value could be +15551234567, but when we display it in our application, we would maybe like to include a html tel-type link and display it with proper spacing such as:

<a href="tel:+15551234567">+1 555 1234567</a>

Or maybe we would use it in a text email, where html wasn't allowed but we would still like the nice spacing such as:

+1 555 1234567

Datarecord provides for all of this by calling the correct function to get the value.

functionresultexample
getRawValue()Get the technical value used when programming+15551234567
getFullValue()Get the value for display in a browser. May include HTML.<a href=“tel:+15551234567”>+1 555 1234567</a>
getTextValue()Get the value for display in a text where HTML isn't allowed.+1 555 1234567
getFormValue()Get the value compatible with a fitting form field.
getTableValue()Get the value for displaying in a table.
getLogValue()Get the value for displaying in a log file.

Object title

A Datarecord object is considered to have a title, which is the human-readable way the object is presented when referred from other places. The most simple way to assign a title is to just pass the is_title property to the Type of the field containing the title. If the title is more complex and depends on information from several fields, then one can also overwrite the getTitle() function.

Convenience functions

Getting a form

One can get a form for editing the object by calling getForm(). This will return a Form object ready to use.

The form will contain all editable fields in the same order that they are defined in the addStructure-function.

If one needs javascript to get the form to behave, then set the $edit_script variable of the class to point to the javascript. In HTML the form will have an ID of [class_name]_form

Complete edit interface

Calling the renderEditComplex($parameters) function will render a complete interface which will list all objects of this type, and allow the user to create, edit and delete these objects.

Also see EditComplex class

References between Datarecords and safe deletes

Some field types allows the linkage to other Datarecord object for example the SingleReferenceType class or the MultiReferenceType class. The first will refer to a single object from another datarecord class and the second will refer to multiple objects. When using these fields, you will also need to specify which other class you want to point to.

If you extended the contact person example above, you could create a separate Datarecord class for the phone numbers and then make these phone numbers reference the contact person. In that way you can register several numbers to each contact person.

This could be an example of such…

CONTINUE FROM HERE

You will also need to go to the foreign datarecord class and add the class name of this datarecord class to either the $referring_classes array or the $depending_classes array. This ensures that the foreign class is aware of the connection. See the chapter about deleting data for an explanation between the two arrays and see the section Integrity check below, for an easy way to check if references are configured correctly.

Using references with non-Datarecord classes

It is possible to use references with non-datarecord classes, as long as these classes implements the DatarecordReferable interface.

Deleting data and references

Platform has a system in place to handle references when deleting data, so that we are not leaving dangling references all around the database.

In order for this system to work, one need to indicate which objects are referring the current object and that can be done by adding these foreign classes to one of the two arrays $referring_classes or $depeding_classes.

Adding it to $referring_classes indicates that the foreign object just refers this object, so if this object is deleted, the reference in the foreign object will be removed (set to 0).

Adding it to $depending_classes indicates that the foreign object depends on this object, and cannot exist without it. That means that if this object is deleted, all foreign objects referring this object will also be deleted.

This logic is nested, so deleting an object can initialize a chain reaction of other deletions and references being removed, keeping your data without dangling references.

Delete strategies

Sometimes you don't want to delete an object if it is referred by anything else. You can handle this by setting a delete strategy.

There are three possible delete strategies:

self::$delete_strategy = self::DELETE_STRATEGY_BLOCK;

This is the default strategy and this will prevent deletion of a Datarecord, if it is still references by other objects in the $referring_classes array. This will make canDelete return an error message if there are still objects referring the queried object, and will likewise prevent delete from deleting anything as long as such references exists, which will force the other delete strategy no matter what. Please note that objects in the $depending_classes aren't considered in this regard. It is possible to specify the $force_purge parameter on the delete command, which will then switch to the next delete strategy.

self::$delete_strategy = self::DELETE_STRATEGY_PURGE_REFERERS

Using this strategy, a call to delete will go over all foreign objects of the classes mentioned in $referring_classes, and remove all references to this object. Furthermore it will go over all foreign objects of the classes mentioned in $depending_classes and outright delete these.

When using this strategy, a call to canDelete will return true even though other objects references this object.

self::$delete_strategy = self::DELETE_STRATEGY_DO_NOTHING

This strategy will delete the current object, but will not affect any referring or depending objects. This can only be used, when working with soft deletion. See below.

Soft deleting objects

Sometimes it can make sense to keep the database record after deleting an object, for example if you are synchronising data to another system, and therefore needs to keep track on what you deleted.

To add soft deletion to an object override the $delete_mode variable with one of these two options:

protected static $delete_mode = Datarecord::DELETE_MODE_EMPTY;

This will add a field is_deleted to the datarecord object, and when the object is deleted, all fields will be emptied except the primary key, the create date and the last update date, and is_deleted will be set to 1.

protected static $delete_mode = Datarecord::DELETE_MODE_MARK;

This will do exactly the same as above, except that no fields are emptied, so when deleting the object, it will remain in the database, but have the is_deleted field updated to 1.

Remember to ensureObjectInDatabase() after doing either of these to build the is_deleted field.

Copying objects

An object can be copied using the copy function in the class, which will create an identical object in the database, with the only exception that the title field of the object (if any) will have “Copy of” prepended.

To make things more interesting one or more related classnames can be added as parameters to the function, and if this is done, all objects of these classes which relates to the main object is also copied and the new set of objects will relate to each other exactly like the source objects.

$family = new Family('Nielsen');
 
$dad = new FamilyMember('Anders');
$dad->family_reference = $family;
 
$mom = new FamilyMember('Becca');
$mom->family_reference = $family;
 
// This will copy the family object, along with the related data.
$new_family = $family->copy(array('FamilyMember'));

Calculations

Sometimes a field in an object can be very expensive to update, either because it requires heavy computation or a lot of database queries. In this case one can request a calculation, which will basically start a background job which will perform the calculation so it doesn't disturb the rest of the script execution. This is accomplished by calling:

$object->requestCalculation('example_calculation');

Be aware that calculations will never be started before the script terminates, so one can never request a calculation to use in the current script.

Of course one would also have to implement the actual calculations which can be done in one of two ways:

Pr. object calculations

To do the calculation object by object, overwrite the doCalculation function. The object passed will be in write mode and will automatically be saved afterwards, so the function just needs to perform the calculation.

public function doCalculation($calculation) {
  switch ($calculation) {
    case 'example_calculation':
      // Do calculation and update necessary fields.
      // Don't save. This will be done afterwards.
      break;
  }
}

Bulk calculations

If a calculation can benefit from being done on several objects at the same time, one can instead overwrite the doCalculationOnObjects function, which is a static function and is just passed the calculation keyword and an array with all object ids which have requested this calculation. In this case one is responsible for both loading and saving the objects.

public static function doCalculationOnObjects($calculation, $ids) {
  // Do some common calculations for all these objects.
  foreach ($ids as $id) {
    $object = new exampleClass();
    $object->loadForWrite($id);
    if ($object->isInDatabase()) {
      // Update object with information from calculation.
      $object->save();
    }
  }
}

If an object is related to another class with a field of type FIELDTYPE_REFERENCE_SINGLE or FIELDTYPE_REFERENCE_MULTIPLE and a change in the object always will require a calculation in objects of the related class, one can specify the calculation property in the field description. This field should name one or more calculations in the foreign class which should be performed any time this object is updated.

'examplefield' => array(
  'label' => 'Reference',
  'fieldtype' => self::FIELDTYPE_REFERENCE_SINGLE,
  'foreignclass' => 'Remoteclass',
  'calculation' => array('calculation1', 'calculation2')
)

This basically tells this object that every time it is updated, it should perform calculation1 and calculation2 on the object pointed to by the field examplefield. This also works if examplefield is changed, as it is checked both when the object is loaded and when it is saved.

If an object is saved but no changes are detected, then the calculation will not be performed.

Keys

Datarecords supports database keys for improved performance. To add a key to a field simply set the key-property to true.

To make a key spanning more than one field, instead set the key-property to the name(s) of the additional fields, separating field names by commas. The current field will be first in the multiple key, followed by the fields mentioned.

A call to ensureInDatabase() will build the appropriate keys.

Integrity check

To ensure that a datarecord class is configured correctly, one can call the static function renderIntegrityCheck on the class. This will render any errors which have been detected in the class setup, and is especially useful to check that references between classes are correctly configured.

This should only be used in a test environment and a good practice is to include a test-script which call this function on all datarecord classes.

Searching objects

Searching with keywords

It is possible to quickly find some objects containing certain keywords, by using the findByKeywords($keywords,$outputformat) function. This will use filtering to search all objects for the keywords by looking in all fields marked as searchable. Each keyword is searched for individually, but phrases can also be searched by encapsulating them in quotes. A search of:

red “hairy cat”

will find all objects containing the word “red” and the phrase “hairy cat” in one or several of the searchable fields.

Output of the function defaults to a Collection but can be altered to be a simple array or output suited for the FieldDatarecordcombobox field type.

Searching with SQL

It is possible to query the database directly, and a good way to do that is using the getCollectionFromSQL($sql) function. Just be sure to always SELECT * from the proper table. This will add all the found records to a Collection and return it.

$datacollection = User::getCollectionFromSQL("SELECT * FROM users WHERE age > 20 AND age < 45");

Access control

A number of methods exist to control what can be done with an object.

// Check if we are allowed to access / view this object.
$object->canAccess();
 
// Check if we are allowed to create a new instance of this object.
Class::canCreate();
 
// Check if we are allowed to copy objects of this class (in general).
Class::isCopyAllowed();
 
// Check if we are allowed to copy this object.
$object->canCopy();
 
// Check if we are allowed to edit this object.
$object->canEdit();

These functions should return true if the operation is permitted, otherwise they should return false. They are all soft-checks meaning that there are no mechanisms build into the Datarecord class to enforce these permissions, so that should be done in the surrounding code. An exception to this is the renderEditComplex() where the rendered interface will adapt to these functions.

If not overridden in a subclass then the functions just returns true.

// Check if we can delete an object.
$object->canDelete();

This behaves a little different than the three other functions, as it is a hard-check meaning that Datarecord doesn't allow to delete an object which doesn't return true on this function. Furthermore the function doesn't return false, if deletion isn't allowed, but instead returns a string with a reason why one cannot delete this object.

Ensure consistency

A good practice is to implement the validateObject() function in a Datarecord class. This function should test that the data inside the object is in a valid state. This function is typically called when something such as the api has manipulated the object data directly, to ensure that the object isn't left in an invalid state.

If you have an object with a $minimum_age and $maximum_age, you can validate that the former doesn't exceeds the latter like this:

public function validateObject() {
  if ($this->minimum_age > $this->maximum_age) return array('Minimum age must not be larger than maximum age');
  return true;
}
datarecord_class.txt · Last modified: 2024/02/14 15:51 by sahl

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki