Creating A New Class in Berylium


An outline of how to add a new class of object to Berylium.

Step 1: Add (or include) the new class in code/berylium-classes.php

Here is sample code for Audio objects, to be explained below. (Huh? See Classes and Objects from the PHP Maual)
class Audio extends ContentObject {
// audio object
var $duration; // duration in seconds
var $format; // format information (type of wav)

// the following implement ID3v1-style tags
var $album;
var $year;
var $track;
var $genre;
var $comment;

// the following are holders for other metadata stored in the original file (or to be stored in e99o translations)
var $id3v2; // carat-delimited key=value pairs of id3v2 tags
var $ogginfo; // carat-delimited key=value pairs for ogg comment header

// constructor
function Audio () {
$this->objtype= "audio";
$this->columns= "duration=INT^format=varchar(255)^album=varchar(255)^INDEX index_album=(album(64))^year=varchar(255)^track=INT^genre=varchar(255)^INDEX index_genre=(genre(32))^comment=varchar(255)^id3v2=text^ogginfo=text";
$this->publishable= 1;
}
}
This code enables audio objects by extending the generic ContentObject class. ContentObject is the highest-level generic class in Berylium, and it defines a set of properties and methods shared by all objects that store actual content, such as folders, documents, and images.

Take a look at the constructor function-- it declares three very important properties, the first two of which *must* be declared by all Berylium objects:
  1. $this->objtype is, most importantly, the name of the MySql table that will store instances of the class.

  2. $this->columns enables the BeryliumObject->makeTable() method to create the audio table in the database, and also tells other methods what properties will be stored. It is a carat-delimited list of key=value pairs where the key is the column name and the value is a MySQL Column type-- see MySQL Create Table Syntax.

    There is a special syntax for specifying column indexes, see the examples for both album and genre. In this case, the key is "INDEX " and the index name, and the value is an index_col_name definition enclosed in parentheses.

    The method of defining columns dates back to the earliest Berylium development days and doesn't even begin to consider the capabilities of a modern MySQL server. If you want to use features that you can't define here, please contact the developers and we'll work something out.

  3. $this->publishable is an optional declaration that instances of this object may be published to a static website. Comment is an example of a class that is not publishable by default, because comments are only ever viewed in list form, not as individual pages.

Step 2: Build Out The Class

Rough out the rest of the class with whatever methods (functions) and properties you think it will need. If you find that you need to make changes to the properties that will be stored in the database, be sure to update $this->columns in the constructor.

In general, the goal is to build properties at runtime where possible, instead of storing them. The next-best thing is to store properties serialized within the "properties" property, by prefixing their names with "p_". Try to only create columns for properties that will need to be used in conditional SQL Query statements, such as SELECT and ORDER BY clauses.

If you need to create a custom method (say, a custom upload() method) that looks like it will duplicate much of the BeryliumObject::upload() method, see if you can get by with calling that parent method either before or after your custom code. It will make your methods much easier to maintain in the long run.

If you need to access other objects in the environment, such as $session or $site, we've found that it's better (despite being classically bad programming practice) to import them into the local scope using a PHP global declaration than it is to refer to them like $GLOBALS['site'].

Notice that most methods will announce themselves to the debug log using a berror() statement near the top. This makes is much easier when something goes wrong to find out where and why it happened.

Step 3: Add An Upgrade to dbupgrade.php

The dbupgrade.php script is used to perform any necessary upgrades to the database on installation of a new codebase. The following lines will check for an existing audio table and create one if not found:
// NEW AUDIO TABLE
$audio= new Audio();
if ($audio->makeTable()) {
print "\nSuccessfully created Audio table.";
}
else print "\nAudio table exists, skipping.";
If existing tables will need to be upgraded with new column definitions, that can be done via dbupgrade.php, but should be avoided at almost all costs. If you are implementing radically new functionality, it will be better to create a new class (as we did with context2) so as to avoid breaking existing installations.

Run dbupgrade.php as follows:
./dbupgrade.php '<dbadminusername>' '<dbadminpassword>'

Step 4: Add CanSave Functionality To Policies

In the ability for users of a certain role to save objects of a particular class must be explicity declared in the policy for that role. This sounds complicated, but it just means that you need to add the following line to the writer and editor policy files:
$this->canSave['audio']= 1;
As a courtesy to developers who may want more liberal policies in the future, you should also add the following to the member and anonymous policies:
$this->canSave['audio']= 0;

Step 5: Create A Minimal Set of Contexts

At the very least, in order to get started, you will need to build contexts for the "create" and "edit" methods. You can start by modifying contexts for an existing class, or you can use the code/contexts/aa-edit-template.be2 template. Generally you want to restrict object creation and editing to writers.
audio-create-writer-html.be2
audio-edit-writer-html.be2

Step 6: Test

This step is a matter of philosophy-- I like to get a like to get a little reward for all my hard work up to this point, so I'll usually login and try creating a new object, just to have some data to play with when building out additional contexts. In this case, http://example.com/be2/audio-.html?method=create will get things started.

Step 7: Customise

Generic (class-neutral) contexts exist for view, index, and list methods, but they generally aren't going to be everything that you want them to be, so the next step would be rebuilding those so that they are specific to your new class:
audio-view-anonymous-html.be2
audio-index-anonymous-html.be2
audio-list-anonymous-html.be2
In this case, we're also going to need view contexts that will serve the attached audio files in wav, mp3, and ogg formats.
audio-view-anonymous-wav.be2
audio-view-anonymous-mp3.be2
audio-view-anonymous-ogg.be2