KTEntity

From KnowledgeTree Document Management Made Simple

Jump to: navigation, search

To avoid having fragile and repetitive SQL access code, KnowledgeTree uses an OR layer built on the KTEntity and KTEntityUtil classes. These handle almost all data access, and hide the low level interactions required to ensure that information is stored correctly.

Contents

Using KTEntity

The best way to describe KTEntity is to walk through a simple Entity class. Much of this is boilerplate, so you can copy this to a new file and start working on with it that way.

require_once(KT_LIB_DIR . "/ktentity.inc");

class ExampleEntity extends KTEntity {
   var $iId = -1;
   var $sMyString;       // starting with s indicates that this is a string
   var $iMyInt;          // i indicates int

   // this _aFieldToSelect array maps the names of the variables to 
   // columns in the database.
   var $_aFieldToSelect = array(
       "iId" => "id",
       "sMyString" => "my_string",
       "iMyInt" => "my_int",
   );

   // this specifies that errors from ->update(), ->create(), ->delete() and friends 
   // should be PEAR::raiseErrors.  This is recommended for ALL new code.
   var $_bUsePearError = true;

   function getMyInt() { return $this->iMyInt; }
   function setMyInt($iNewValue) { $this->iMyInt = $iNewValue; }
   function getMyString() { return $this->sMyString; }
   function setMyString($sNewValue) { $this->sMyString = $sNewValue; }    

   // this returns the table name for this entity.  See "SQL Storage" below
   // for an explanation of KTUtil::getTableName, and how to structure tables
   function _table () {
       return KTUtil::getTableName('workflow_trigger_instances');
   }

   // these static methods are boilerplate - you need to change the class name
   // only when copying to a new class.

   // STATIC
   function &get($iId) { return KTEntityUtil::get('ExampleEntity', $iId); }
   function &createFromArray($aOptions) { return KTEntityUtil::createFromArray('ExampleEntity', $aOptions); }
   function &getList($sWhereClause = null) { return KTEntityUtil::getList2('ExampleEntity', $sWhereClause); }

}

Things to note:

  1. There is no constructor defined - this makes using the entities a little easier.
  2. The functions under "static" call KTEntityUtil. See further down for more detail on what that class lets you do, but for now you just need to note that the class name needs to be passed through in each of these calls - don't forget to change it to the name of the class you're defining!
  3. All the static methods should be checked for PEAR::raiseError results after being called.

So, once we've defined this class (and assuming that the SQL is in place, etc.), we have a clean mapping between an object and the database. The next obvious question is how we use the class.

To get an instance of a ExampleEntity with ID 1, you would do:

$oExample = ExampleEntity::get(1);
if (PEAR::isError($oExample)) { 
  // handle the error
} 

It is very important to check that the result is not an error. There are lots of ways of getting a dud ID from the system - old data, bad integrity, poor input, hostile attack by a user.

To create a new ExampleEntity:

$oExample = ExampleEntity::createFromArray(array(
   'mystring' => 'this is a string',
   'myint' => 1,
));
if (PEAR::isError($oExample)) {
   // handle error.
}

Note here that the entries in the array are the same as the entries in _aFieldToSelect without the leading character. By convention, these are used lowercase, though the system lowercases all variable names to prevent typos.

To obtain a list of all ExampleEntities, you would call

$aEntities = ExampleEntity::getList();
if (PEAR::isError($aEntities)) {
   // handle error
}

Very often you'll actually want a subset of the entities (e.g. all the entities with my_int = 5). This is best handled by adding another method to your entity (e.g. getByMyInt($iInt) ) using the helper methods from KTEntityUtil. (see below).

How it is implemented

This is implemented in lib/ktentity.inc

There are two classes:

  • KTEntity - entities inherit from this and end-user-code uses this
  • KTEntityUtil - utility functions to help KTEntity and KTEntity-derived classes, not for use in end-user-code

KTEntity

Attributes

  • bUsePearError - defaults to false, for compatibility reasons, but all new classes should set it to true for better error handling

If true, PEAR_Error objects are returned instead of false on update and create. This makes it easier to figure out why action failed.

Methods

  • create - Save a newly created not-in-the-database object to the database. Before running this, the object doesn't have an id.
  • update - Save a object from the database back again.
  • delete - Remove an object from the database.
  • newCopy - copy an existing (usually) from-the-database object so that it can have the create method run on it to save a new object to the database
  • load - load the attributes of the object from the database based on the id given, or if no id is given, the id in the object already.
  • loadFromArray - load the attributes of the object from an associative array (dictionary)
  • updateFromArray - update a from-the-database object with attributes from an associative array (dictionary)

Derived object methods

Due to the simplicity of PHP4's object model, the following methods are expected to be created by every entity that inherits from KTEntity, generally with only the table name or class name changed:

  • _table - returns the table name. Usually it will contain a line like this:
 return KTUtil::getTableName('permission_dynamic_conditions');
  • get - returns the object by id. It's implementation looks like this:
 function &get($iId) {
     return KTEntityUtil::get('KTPermissionDynamicCondition', $iId);
 }
  • createFromArray - returns the created object from the associative array given.

Implemented like this:

   // STATIC
   function &createFromArray($aOptions) {
       return KTEntityUtil::createFromArray('KTPermissionDynamicCondition', $aOptions);
   }
  • getList - returns the list of objects that match the where clause given.

Deprecated in favour of getBy... style functions

Implemented like:

   // STATIC
   function &getList($sWhereClause = null) {
       return KTEntityUtil::getList2('KTPermissionDynamicCondition', $sWhereClause);
   }
  • get*, set* attribute getters/setters

Implemented like:

 function getFoo() { return $this->iFoo; }
 function setFoo($mValue) { $this->iFoo = $mValue; }
 ...
  • getBy* - returns list of objects that match a given attribute and value.

Preferred, since it creates less of a tie between the calling code and the database layout.

Implemented one of two ways:

   function &getByNamespace($sNamespace) {
       return KTEntityUtil::getBy('KTAuthenticationSource', 'namespace', $sNamespace);
   }
   function &getByFieldsetAndName($oFieldset, $sName) {
       $iFieldsetId = KTUtil::getId($oFieldset);
       return KTEntityUtil::getByDict('DocumentField', array(
           'parent_fieldset' => $iFieldsetId,
           'name' => $sName,
       ));
   }

KTEntityUtil

These are all static/utility methods. KTEntityUtil is never instantiated.

ie, use looks like: KTEntityUtil::function_name

  • getList2 - calls getList with the table associated with the class given (creates one less place table names are stored out of the classes themselves)
  • getList - given a where clause (as a string or a parameterised query), create an object for each row returned from the table using that where clause
  • createFromArray - helper for KTEntity-derived classes to implement their createFromArray method
  • updateFromArray - currently unused - can be used to update a row without explicitly creating the object and calling its updateFromArray method
  • loadFromArray - currently unused - can be used to load an object without explicitly creating the object and calling its loadFromArray method
  • loadFromArrayMulti - currently unused - used by getList to load multiple objects from the database in one go, but nothing uses the 'fullselect' option that would use this code path.
  • get - helper for KTEntity-derived classes to implement their get method

Note, this does _not_ return an object of the class given, but rather an object of a proxy class, that allows for multiple instances of a particular id of the proxy class but only one instance of that id for the class itself.

It's like this to work around the numerous pitfalls of PHP4's defaulting to passing objects by value. In essence, this makes it as if entity classes are always passed by reference (except when they're not, of course - like if you get access to the actual object somehow and break the rules).

  • getBy - helper for KTEntity-derived classes to implement their getBy* methods.

This contains a very basic query builder that allows for queries to be built without knowledge of too much back-end database information.


getBy

Parameters:

  • $sClassName - the class of the objects to be created from the query results
  • $aField - the fields used by the query builder (usually, the output of array_keys($aDict))
  • $mValue - the values used by the query builder (usually, the output of array_values($aDict))
  • $aOptions - options that affect the return from the class. Of these, the following options are the most used:
    • 'multi' => true, means that it is valid for this query to return multiple results
    • 'noneok' => true, means that it is valid for this query to return no result (implied by multi by default, but can be changed)
    • 'ids' => true, means that the ids from the database will be returned, not objects.

Fields are usually the names of the columns in the database that they affect.

Values are usually the value in the column.

So, if field is "name" and value is "foo", then the generated query part will be:

   name = "foo"

Actually, it uses parameterised queries, so it returns:

   array("name = ?", "foo");

If field is an array of ("name", "age") and value is an array of ("foo", 7), the effective result is:

   name = "foo" AND age = 7

The actual parameterised query result is:

   array("(name = ?) AND (age = ?)", "foo", 7)

$aField can either be a list of field names, or a single field name. It will be converted internally to a list of field names.

Similarly, $mValue can either be a list of values, or a single value. Again, it will be converted internally to a list of values.

Each value in $mValue can either be a single value, or an associative array. Internally, it is converted into an associative array.

The associative array has the following options:

  • type - defaults to equals if a single value is given. The type of comparison.
  • value - the value given if a single value is given.
  • field - the field to work on, in case the field given in $aField is different (say, if there are two comparisons on the same database table)

The query is built by calling KTEntityUtil::_getby_$type for each value, and then putting parentheses around each component and placing "AND" between them.

  • _getby_equals generates "field = value", if value is a scalar (not an array)
  • _getby_equals generates "field IN (values)", if value is an array
  • _getby_nequals generates "field != value", if value is a scalar (not an array)
  • _getby_nequals generates "field NOT IN (values)", if value is an array
  • _getby_after generates "field > value"
  • _getby_before generates "field > value"
  • _getby_between generates "field BETWEEN value[0] AND value[1]"

SQL storage

getTableName

When referencing table names, the KTUtil::getTableName utility function must be used. This will later allow KnowledgeTree to work with having all tables prefixed by a user-supplied value.

Caching and Proxies

Caching

Automatic caching of objects and results

The KTEntity and KTEntityUtil functions automatically use an on-disk cache for results of queries. This allows for database latency and some CPU time to be reduced in some cases, in exchange for extra disk IO.

The caches are built on loading objects from the database, and for the results of getList/getBy/getByDict.

The caches are automatically cleared on object creation, deletion, or modification.

Manual intervention

When you break the rules and modify the database directly using UPDATE or DELETE statements that affect multiple objects, instead of using the update or delete methods on objects, the cache will be out of sync. Unfortunately, bulk UPDATE or DELETE statements save a lot of time. If you need to do this, use the KTEntityUtil::clearAllCaches utility function to empty the cache for a particular class.

Proxies

PHP4's object model means that when objects are passed to functions that by default they are passed by value. If there is a mistake in terms of passing the object by reference, there are now two or more objects that behave totally separately and are modified separately and which become out of sync with each other. This is almost never what is wanted when dealing with entities. You want a single entity for a particular row in a table, identified by an ID.

The KTEntityUtil::get utility function is used by entities to implement their get method. This function doesn't return an object of the class requested, but instead returns a proxy. All functions on the proxy are performed on a single instance of the entity, no matter how many proxies exist.

The only problem this introduces is that direct attribute manipulation of the proxies returned by get doesn't have any meaningful effect. The accessors/mutators (getters and setters) must be used.

Personal tools