Username:
Password:
    Forgot your password?
Member Login

Programming in Sitellite

Notes

Chat Loading chat status
  • Please subscribe to chat.
  • Older messages can be viewed in the chat archive.

Subscribe  |  The Lounge  |  Share Lesson

Chapter 5: The Class Loader and Database Essentials

Page:   1 2 3 4 >



When writing applications in any language, it's easy to amass a large number of scripts very quickly that often have duplicate bits of code that would be better placed in a single, shared location. To solve this, we use PHP classes to abstract repetitious tasks so that we only write them once, then use the class in each place that the task is needed.

Loading classes from SAF

Sitellite provides a convenient function for importing classes from the SAF library. Let's create a new box and play around with the 'saf.HTML' library to illustrate this:

<?php

loader_import ('saf.HTML');

echo html::p (
    html::strong ('Some text'),
    array ('style' => 'text-align: center')
);

?>

Save this to inc/app/myapp/boxes/classtest/index.php.  Also make sure to copy the access.php file into your new box, or move it into the main 'boxes' folder so it is inherited by all your app's boxes.

To load this box in your browser, go to:

  • http://www.example.com/myapp-classtest-action 

Calling your own classes

Writing your own classes in Sitellite is the same as in any PHP application. Here's a simple example class. Save this into a file named MyList.php in your app's 'lib' folder.

<?php

class MyList {
    var $list = array ();

    function get ($name) {
        return $this->list[$name];
    }

    function set ($name, $value) {
        $this->list[$name] = $value;
    }
}

?>

Loading your custom class

Let's modify the 'classtest' box to call our new custom class. In Sitellite, you can load your own class libraries with the same function used to load the core SAF libraries.

<?php

loader_import ('myapp.MyList');

$list = new MyList ();

$list->set ('foo', 'bar');

echo $list->get ('foo');

?>

A little cross-app integration

Let's make it a bit more interesting and use a library from another app in our own. Here we're going to import the built-in news app's news.Story library which defines a NewsStory class, and we'll use that to display a list of upcoming news stories in our own app.

<?php

// import the library itself
loader_import ('news.Story');

// create a new object
$story = new NewsStory;

// set a few properties for performing a story search
$story->limit (10);
$story->orderBy ('date desc, rank desc');

// we can get the latest 10 stories with a blank search
$list = $story->find (array ());

// let's output the stories with a template
echo template_simple ('latest_stories.spt', $list);

?>

Finishing up the example, we can create a quick template that shows the stories in a bulleted list. Here the loop tag refers simply to 'obj' since the list itself was passed directly to template_simple(). Also notice the use of {site/prefix}, which refers to the prefix property of the global $site object. You can refer to the various global objects in your templates just like this.

<ul>
{loop obj}
    <li><a href="{site/prefix}/news-app/id.{loop/id}">{loop/title}</a></li>
{end loop}
</ul>

Exercise: As you can see, Sitellite makes it easy to integrate components of one app with another. Look up the loader_box() function and describe the various ways of calling boxes from other boxes, from .spt templates, and in global templates as well.

 

Page:   1 2 3 4 >



Page:   < 1 2 3 4 >



The Generic class

The Generic class is part of the saf.Database package.  It provides a set of generic accessor methods for reading and writing to database tables, which can drastically reduce the amount of effort required to write database-bound code, which is a frequent endeavour in most applications.

Generic also provides features that allow objects to be automatically generated in a similar way as forms are with INI descriptors, and provides automatic integration with Sitellite's access control and multilingual features with no extra coding.  But first, let's look at a simple example and build up from there.

Creating a database table

To begin using the Generic library, we'll need to create a database table to work with. To do so, log into Sitellite and go to the Control Panel in the top menu. Next, select the DB Manager under the Tools menu in the top right and click on the SQL Shell link. This should pull up a text box which you can use to execute the following SQL code:

create table myapp_listing (
    id int not null auto_increment,
    name char(48) not null,
    description text,
    primary key (id),
    index (name)
);

Using Generic with our new table

We're now ready to make use of the Generic class. The most direct way to do this is to import it and extend it in a class of your own. Enter the following into a file named Listing.php in your app's 'lib' folder.

<?php

loader_import ('saf.Database.Generic');

class MyappListing extends Generic {
    function MyappListing () {
        parent::Generic ('myapp_listing', 'id');
    }
}

?>
This simply imports the Generic package and creates a blank class extending it.  The parent::Generic() call is passed the database table name and the primary key field name.  That's all Generic needs to work with our new table.

From here it's easy to import our class and begin adding and retrieving data from the myapp_listing table. This bit of code illustrates how to add, modify, remove and retrieve using the Generic methods.

<?php

loader_import ('myapp.Listing');

$listing = new MyappListing ();

// add a few listings...

if (! $listing->add (array (
    'name' => 'Listing One',
    'description' => 'Description of the first listing.',
))) {
    die ($listing->error);
}

$id = $listing->add (array (
    'name' => 'Listing Tw',
    'description' => 'Description of the second listing.',
));

// update listing two (to correct our spelling!)

$listing->modify ($id, array ('name' => 'Listing Two'));

// display all of the listings

echo template_simple (
    'listings.spt',
    $listing->find (array ())
);

// delete them all

$listing->remove (array ('1=1'));

?>

Before we preview this, let's create the listings.spt file referenced in the above script, so we see the output of the listings shown when we refresh our browser.

{loop obj}
<p>
    <strong>{loop/name}</strong><br />
    {loop/description}
</p>
{end loop}

Exercise: What Generic method retrieves a single object from the database if you give it the primary key value for that object?

 

Page:   < 1 2 3 4 >



Page:   < 1 2 3 4 >



Defining multiple tables and their relations

Defining each object manually as we did above is fine if we only have one or two, but Generic offers a much more powerful way of defining objects. First, let's create a few more database tables for this example.

create table myapp_products (
    id int not null auto_increment,
    name char(72) not null,
    price decimal(7,2) not null,
    category int not null,
    description text not null,
    primary key (id),
    index (category, price)
);

create table myapp_categories (
    id int not null auto_increment,
    name char(72) not null,
    primary key (id),
    index (name)
);

The Objects.ini.php file

Next, create a file in your 'lib' folder named Objects.ini.php with the following info.

; <?php /*

[Product]

table = myapp_products
pkey = id

[Category]

table = myapp_categories
pkey = id

[rel:Category:Product]

type = 1x ; one-to-many
Product field = category
cascade = on ; delete products when a category is deleted

; */ ?>

The first two blocks are the equivalents of the class definition we created earlier. The third 'rel' block defines a one-to-many relationship between the two tables. Generic also supports many-to-many relationships as well. Supposing a joining table of the form:

create table myapp_product_category (
    product_id int not null,
    category_id int not null,
    primary key (product_id, category_id)
);

A many-to-many relationship could be specified as follows:

[rel:Category:Product]

type = xx ; many-to-many
join_table = myapp_product_category
Product field = product_id
Category field = category_id

Back to our original example, we can now include these objects just like this:

<?php

loader_import ('myapp.Objects');

$category = new Category;

$cat_id = $category->add (array (
    'name' => 'Hosting Packages',
));

$product = new Product;

$product->add (array (
    'name' => 'Basic Hosting',
    'price' => 9.95,
    'category' => $cat_id,
    'description' => '250MB storage, 5GB bandwidth, etc.',
));

?>

Table relations

We haven't looked at table relations yet, but Generic defines these automatically from the above description file. Based on the above relation, six new methods are available to us now:

Product::setCategory (&$category_object)
Product::unsetCategory (&$category_object)
Product:getCategories ()
Category::setProduct (&$product_object)
Category::unsetProduct (&$product_object)
Category::getProducts ()

Let's look at a couple ways we might rewrite the hosting packages box above using these relations. Note that I've also used the shortcut of defining a new item by passing it to the constructor as well, instead of calling add() explicitly. You can also pass the primary key value to the constructor to retrieve it immediately instead of calling get().

<?php

loader_import ('myapp.Objects');

$product = new Product (array (
    'name' => 'Basic Hosting',
    'price' => 9.95,
    'description' => '250MB storage, 5GB bandwidth, etc.',
));

$category =& $product->setCategory (
    new Category (array (
        'name' => 'Hosting Packages',
    ))
);

?>

Alternately, we could write this from the category first:

<?php

loader_import ('myapp.Objects');

$category = new Category (array (
    'name' => 'Hosting Packages',
));

$product =& $category->setProduct (
    new Product (array (
        'name' => 'Basic Hosting',
        'price' => 9.95,
        'description' => '250MB storage, 5GB bandwidth, etc.',
    ))
);

?>
Also note that the set*() methods return a reference to the Generic object being passed to them, allowing you to pass them a new object, but to still capture it and manipulate it further afterwards.

Page:   < 1 2 3 4 >



Page:   < 1 2 3 4



Updating a single item

To update a single item using Generic, you can easily do this with the following code. This could be integrated, for example, into a form submission for updating content.

<?php

loader_import ('myapp.Objects');

// get the specified product

$product = new Product ($_GET['id']);

// modify the product in place

$product->set ('name', $_GET['name']);
$product->set ('price', $_GET['price']);
$product->set ('description', $_GET['description']);

// save the changes to the database

$product->save ();

?>

Single product display

Another common use case is to display a single item in a box, such as a product details page. This can be done in just a couple lines of code. The makeObj() method returns a non-Generic object like you would ordinarily get back from a database query.

<?php

loader_import ('myapp.Objects');

$product = new Product ($_GET['id']);

echo template_simple ('product.spt', $product->makeObj ());

?>

Permissions/access control

Now let's say we want to integrate our code with Sitellite's permission system so we can use it to ensure only the right people are able to see and even modify the data we're producing. To do this, we're going to have to modify our myapp_products table to include the necessary access control fields. The following SQL should fix that up.

alter table myapp_products add column sitellite_status varchar(48) not null default '';
alter table myapp_products add column sitellite_access varchar(48) not null default '';
alter table myapp_products add column sitellite_team varchar(48) not null default '';
alter table myapp_products add index (sitellite_status, sitellite_access, sitellite_team);

Now for the big change. Open up the lib/Objects.ini.php file and add the following line to the [Product] block:

permissions = on
I'm afraid that is actually all there is to it.  Requests made to find() items from the product list will automatically be limited by Sitellite's access controls.  Generic ties into Sitellite's saf.Session package for that.

Multilingual objects

Let's take this one step further and make our products translatable as well. To do this, we'll need one more line in lib/Objects.ini.php in the [Product] block. You could add this to [Category] as well for thoroughness. Note that for the tables to be translated in Sitellite's translator interface, we'll also need to define content types (aka collections) for them, which are covered later on in the lesson.

multilingual = on

Custom class methods

Say you want to extend your Generic objects with your own custom methods. Generic can do that too. First, we'll let Generic know where to find our custom code in the lib/Objects.ini.php file. Simply add these two lines to the [Product] section.

import = myapp.CustomProduct
extends = CustomProduct

From here, it's a lot like how we manually created our first Generic-based object. We'll take the 'CustomProduct' name that we defined above and put the following in a lib/CustomProduct.php script:

<?php

loader_import ('saf.Database.Generic');

class CustomProduct extends Generic {
    // let's add a calculateTaxes() method here
    function calculateTaxes ($percent) {
        return $this->val ('price') * $percent;
    }
}

?>

The one difference here is that we don't have to worry about a constructor or telling Generic what table information to use, since we do that in the lib/Objects.ini.php file already. We simply create the shell of a class and add methods to it.

Generic leads to short, simple, and highly readable box code that is obvious in its function and easy to maintain. It also encourages a more disciplined programming technique, since accessing your data through the Generic objects helps enforce the Model part of MVC.

And as you can see, it's quick due to the minimal amount of code that offers fairly deep integration with Sitellite's various components.

Performance Tip: If you chmod your 'lib' folder to be writeable by Apache (usually 0777), then the auto-generated code will be saved to lib/_Objects.php so it's only generated dynamically on the first request.  If you make changes to the lib/Objects.ini.php file afterwards, Sitellite will automatically update the generated code as well. 

Lazy database programming

Say you just want to get something from the database directly, without all the setup. No worries. Sitellite provides some very handy database access functions which talk to the global $db object for you, making most database tasks simple one-liners. For example:

<?php

// insert a product into the database
db_execute (
    'insert into myapp_products values (null, ?, ?, ?, ?)',
    $_GET['name'],
    $_GET['price'],
    $_GET['category_id'],
    $_GET['description']
);

// get the last inserted id value
$id = db_lastid ();

// fetch all of the items in a certain category
$list = db_fetch_array (
    'select * from myapp_products where category_id = ?',
    $_GET['category_id']
);

// get a single product object
$product = db_single (
    'select * from myapp_products where id = ?',
    $_GET['id']
);

// get just the price of a single product
$price = db_shift (
    'select price from myapp_products where id = ?',
    $_GET['id']
);

// get a list of key/value pairs for the products (id and name)
$list = db_pairs (
    'select id, name from myapp_products order by name asc'
);

?>

Page:   < 1 2 3 4



Chapter 6: Custom Content Types (Collections) »