One of the goals of GenHelm is to enable sophisticated websites to be implemented without requiring any programming. Nevertheless, if you are planning to implement more than just a brochure style site chances are you will need to do some PHP programming. 

Writing PHP programs in GenHelm is likely different from what you are used to if you have been writing typical PHP programs by hand. In this help we summarize the key differences to be aware of.

Creating an Online Store

A common use case for websites is to build online stores. GenHelm provides lots of built-in classes such as a shopping cart and credit card payment handlers. This functionality is described within a separate dedicated help menu. Even if you are not planning to build an online store you should refer to this section to get more ideas regarding programming with GenHelm. Follow this link to learn about building e-commerce sites in GenHelm.

No Entry Point Programs

Some programmers my be familiar with systems that pass the name of PHP programs as part of the URL. For example, web address www.example.com/order.php might be use to invoke a backend program named order.php. Although there is technically nothing preventing you from launching certain programs this way it is unnecessary and strongly discouraged. Normally, all pages supported by GenHelm are launched by a common index.php program that contains the following code:

<?php
require('../../webstart.php');
?>

This approach has several advantages including:

  1. It eliminates almost all code from residing under the public area of your website (where it is less secure).
  2. It allow for all URL parsing and routing to be handled centrally by the framework.
  3. This ensures that all of your websites and pages are handled in a consistent manner.

Almost Everything is a Class

Although GenHelm allows you to create .inc files using the php_include model, 99% of the code you write will be within PHP classes. There are three main models that you will be using to develop custom classes.

php_class

This model can create any custom class that you require. You can define all of your class properties declaratively and automatically generate get and set methods.

custom

This model is used to build web pages that don't fit the pattern of any other page oriented model. Since custom pages involve more work than most other types of pages, make sure that none of the other models will do what you need before deciding to use the custom model.

form_handler

If you are developing code to interact with an HTML form (created by the form model) you will use the form_handler model.

php_trait

This model can be used to define traits to be shared amongst the three types of classes described above.

Refer to the help of each of these models for specific details about each model. Generally speaking, you should avoid writing a lot of code directly within your custom pages and form_handlers. Usually, a better approach is to encapsulate the code within a php_class component and call methods of this class from within the custom page and the form_handler. This will make your classes more resusable and flexible since they won't be tightly coupled to the interfaces used by custom pages and form handlers. 

Extending the standard_base Class

Most complex classes should inherit from standard_base located in system. This provides a useful set of methods for message and error handling.  You can find help for the inherited assign_message method within the messages model help. As an alternative to inheriting standard_base, you can implement similar functionality by using the system trait named standard_messaging.

Passing the site Object

Except for trivial classes, Almost every class you write is likely going to require access to the site object.  If your class extends standard_base it inherits the site object automatically. If it does not, you will likely want to explicitly define site as a property of your class as follows:

Sample site property

In most cases this can be defined as Protected but, depending on how you need to use the site class there may be cases where it needs to be defined as public.

There are two main ways to instantiate a class:

  1. Manually instantiating the object.
  2. Using the create_object method of site.

Let's review these options next.

Manual Instantiation

Classes Residing in the Current Site

This is similar to what you might expect. Here is an example of how you would instantiate a class that is specific to your website:

require_once SITE_CLASS_PATH.'invoice_api.php';
$invoice_api = new invoice_api();
$invoice_api->set_site($this->site);

Notice that we use the defined variable SITE_CLASS_PATH since this always points to our site's private_data/classes folder. Never "hardcode" directories because you want your code to run in both a sandbox environment and a production environment without change.

In this example, we are calling the set_site method to pass the site object to the new object.  If the class you are instantiating inherits from standard_base it automatically defines the following constructor:

function __construct($site=null) { // Allow site to be passed optionally at instantiation
	if (is_object($site) and get_class($site) === 'system\site') {
		$this->site = $site;
	}
}

In such a case you could pass the site object when the object is created as in:

require_once SITE_CLASS_PATH.'invoice_api.php';
$invoice_api = new invoice_api($this->site);

If you are not inheriting from standard_base you can always code your own __construct method to allow the site object to be passed in at the time the class is instantiated.

Classes Residing in system

Instantiating a system class can be done in a similar manner except you would define the path using CLASS_PATH as in:

require_once CLASS_PATH.'mailform.php';
$mailobj = new mailform($this->site);

It is important to note that some of the system classes are defined within the namespace system while others are not. The system namespace is generally used for class names that are generic and more likely to collide with classes you may want to write for your application. Here we see an example of instantiating a system class that uses the system namespace.

require_once(CLASS_PATH.'array_to_table.php');
$html_table = new \system\array_to_table();

Using the site Object's create_object Method

A second way to instantiate an object is to call site->create_object('class_name'). This method is slightly slower than the manual method however it offers the following advantages:

  1. You don't need to require the class program before using this method since the code takes care of this.
  2. The method implements an automatic path override protocol such that if the class is found within the current site, this will be used. If the class is not within the current site and the site has indicated an "inherit From Site" in site_settings, this site will be checked next. Finally, if the class is still not found the method will look for the class in system. Taking advantage of this mechanism allows you to override classes defined in system such that the current site's version of the class will be automatically picked up ahead of the system version without changing any of the code that instantiates the class.
  3. The create_object method automatically calls the set_site method of the instantiated object (if this is defined) passing it a reference to the site object.
  4. This method of instantiating an object can persist the object's data across page requests by saving it within a session variable and reloading it from the session.
  5. create_object supports a soft fail feature to support optional classes. When this feature is enabled, a runtime error will not be raised if a class is not defined.

Let's review the full list of parameters supported by create_object. 

function &create_object($class_name,$industry='',$persist=false,$subdirectory='',$soft_failure=false)

Notice that create_object returns an object reference and has one required parameter.

class_name

The supplied class name must correspond to the name of a .php file within the private_data/classes folder or one of its subfolders. For example, if you are building a site within the directory cafedeflore which inherits from the pseudo site named restaurant and if you supply a class name of dinner_menu, by default the system will look for the following files (in order of preference):

  1. private_data/cafedeflore/classes/dinner_menu.php
  2. private_data/restaurant/classes/dinner_menu.php
  3. private_data/system/classes/diner_menu.php

If the class is found within an inherited site or system, the create_object method will first try to load the class using the namespace that matches the directory in which the class was found (either restaurant or system in our example). If it can't be loaded using this namespace a second attempt will be made to load the class, this time without a namespace.

industry

If an industry value is supplied, this will bypass any attempt to look for the class within the current site and, instead, go directly to the supplied folder. The industry value can be any site directory under the private_data folder, it does not need to match the inherited site specified in site_settings. The following special keywords (strings) can also be used to specify the industry:

SITE Only look in the current site's classes folder
FRAMEWORK Only look in the system classes folder

persist

If persist is set to true, this will enable sessions and cause the contents of the requested object to be saved to the session before the current page request ends. This option will also cause the specified object data to be loaded from a previously cached session if this exists.

The persist value can also be passed as an array to provide more control over this feature. The array may contain the following keys:

key By default the object will be cached using the class name as the key. In such a case, only one instance of the class may be persisted. You may pass an alternate key value to remove this limitation.

new This option can be used (set to true) to force a new object to be created even if there is already a cached version of the object.

save Set the save key to true to request that the object be cached at the end of the current request. This is the default action when using the persist parameter so the save key is only needed when it is assigned the value false to prevent saving.

required If required is set to true, and no cached object is found, the user will be redirect to the home page and a session expired message will be shown.

subdirectory

If the class does not exist directly within the classes folder, but rather a subfolder of classes, enter the path below classes where the class is located.

soft_failure

If you do not want a hard failure (error condition) to be raise if the requested class cannot be located, set the soft_failure option to true.

Requirements for Caching Objects

Classes that are persisted must implement a method named pre_sleep. This method will be called before the object is serialized to the cache. At a minimum, this method should unset its instance of the site object so this does not have to be cached. Additionally, this method should clear any other class properties that do not need to be persisted across pages. Here we see an example of this method:

Function pre_sleep() {
	 unset($this->site,$this->session_manager);
 }

PHP will automatically call the __wakeup method of the unserialized object if such a method is defined. The GenHelm framework will automatically call the set_site method when the object is unserialized from the session cache so you can use this method to perform any initialization activities.

Handling Nested Classes

The object caching feature supports objects that contain nested objects. Nevertheless, for performance reasons and memory limitations, you should avoid caching excessively large objects with lots of class references. Definitely avoid caching the site object since this is unnecessary. When caching objects that contain nested objects it is necessary for the pre_sleep method to return a list of classes to be "required" before the main object is unserialized. This will ensure that all classes are declared automatically whenever the main cached object is restored so that nested classes are not considered incomplete by PHP.

Generally, the pre_sleep method of your main class should call the pre_sleep methods of all of the nested classes and assemble the required class files to be returned to the GenHelm framework. Here we see an example of what a main pre_sleep method might contain:

function pre_sleep() {
  // Initilize any lists that may not get assigned later
  $material_req = $hem_req = null;

  // Set require for Material Object class
  $require[] = SITE_CLASS_PATH.'material_object.php';
  if (isset($this->material)) {
    // Call Material Object pre_sleep to allow it to return any required nested classes
    $material_req = $this->material->pre_sleep();
  }

  // Set require for Hem Object class
  $require[] = SITE_CLASS_PATH.'hem_object.php';
  if (isset($this->edge)) {
    foreach ($this->edge as $hem) {
      // Since there are multiple hem objects call each one in case they want to do special pre_sleep handling.
      // We only need to keep the return parameter for the last call since they will all be the same.
      $hem_req = $hem->pre_sleep();
    }
  }

  // Dereference any objects that don't need to be cached
  unset($this->site,$this->cost_calculator,$this->unit_converter);

  // Cast the returned values to arrays since some nested objects may return null - array_merge will ignore these.
  return array_merge($require, (array) $material_req, (array) $hem_req);
}

If the nested class does not contain additional nested objects it can just dereference any objects that it does not want to cache and return null as shown here:

function pre_sleep() {
	unset($this->site);
	return null;
}

On the other hand, if it has embedded objects to be cached it must return the require statements needed to define these nested objects as shown in this example:

function pre_sleep() {
  	unset($this->site,$this->material_calculator);
 	return array(SITE_CLASS_PATH.'material_widths.php',SITE_CLASS_PATH.'material_pricing.php');
}

No echo Statements

Generally speaking your PHP classes should never use the echo statement. The one exception might be for temporary debugging purposes. This is because the framework takes care of page rendering so echo output will always be positioned at the top of the page buffer and could interfere with the setting of http header parameters which must be set before any other output. Normally, temporary debug data should be shown by calling the site object's debug method as in:

$this->site->debug('Order:',$order_number,'Details:',$order_details);

If you have an alternate PHP editing environment that supports debugging this can also be used to step through your code line by line and monitor variable contents. GenHelm even allows you to make code changes while inside other editing programs provided your changes are made within the custom code sections of the generated program(s). These code changes will automatically be used to update the specification for the programs when they are later read into GenHelm. You won't be able to promote your changes to production until you have regenerated the programs in GenHelm in order to sync the specification with the generated code.

Calling Dollar Functions

All dollar functions can be called programmatically as needed. There are two site methods that can be used to resolve dollar functions.

simple_dollar_function($contents)

This method accepts a string that contains one or more dollar functions and it returns a string with the dollar functions resolved. Here is an example:

$string = 'Today is $date(,,day) the time is $time(,,12:mm ap)';
$resolved = $this->site->simple_dollar_function($string);

dollar_function($function_name,...)

This is the preferred function to use when calling a specific dollar function. There are two means by which parameters can be passed to this function:

Using Individual Parameters

This method works best when calling dollar functions that only require a few parameters. Using this approach you should pass the parameters in the same order as you would when embedding dollar functions in your content. Pass null for optional parameters that you do not wish to set. This is only needed if you are skipping over parameters, optional trailing parameters can be left off entirely. Here we see an example using the $date.

$resolved = $this->site->dollar_function('date',null,null,'day');

Using a Keyed Array

When the dollar function to be called accepts a lot of parameters it is best to call the function using this method. In addition to passing the name of the dollar function you also pass an keyed array containing the parameters to be passed. You can skip any optional parameters. This keyed array is normally built by using the dollar function help form.

$dbselect = array(
'return_format' => 'array',
'return_column' => array('identifier','website','displayname','displayname_html','address1','address2','city',
                         'region','country','postalcode','phone','email','lat','lng','firm_name','special_map_icon'),
'schema' => '',
'table' => 'members',
'key' => array('map_page','hide'),
'value' => array($country,'0'));
$result = $this->site->dollar_function('dbselect',$dbselect);

Determining Physical Folder Names

GenHelm developed code is able to be moved from one physical location to another without change because file locations are never hardcoded within the programs. If you need to know the physical location of a folder you should always call the site object's get_site_path method. Here is the signature for this method:

function get_site_path($directory,$site_directory='',$public_path=false)

A typical use case for this might be that you wish to store a file in the site's data folder. The call below could be used to build the file name. 

$full_file_name = $this->site->get_site_path('data').'orders.txt'

Note that the returned path always includes the trailing slash. The second parameter can be used to requires a folder of another site directory. You can pass 'INHERIT' to refer to the industry site or 'FRAMEWORK' to refer to the system site. By default, the following $directory values are assumed to be under the public document path:

audio
cache
docs
fonts
images
styles
support
track
video

You can also pass public_root to obtain the public root directory. If you want a public root directory other than those listed you must set the $public_path variable to true.

All other directory names will return a path under the private_data folder. You can also pass private_root to obtain the private_data root directory of the site. Note that the returned directory may not exist.

Determining a Site URL

The site object's get_site_url method can be used to build a url for the current site. Here we see the signature for this method:

function get_site_url($which='',$add_trailing_slash=true)

By default, this will return the root url. You can pass parameter one to add a path onto the root url. By default, a trailing slash will be added unless false is passed in parameter two.

Encrypting URLs

Sometimes you may want to keep information contained in the URL private for security reasons. This can be done in several ways. Let's look at the three common techniques for encrypting page URLs. 

  1. By setting the page's Encrypt URL option (in Advanced Page Properties) to Required.
  2. By setting the encrypt option when calling the $link function.
  3. By calling the $encrypturl function.

Here is an example of the second option:

$link = array(
  'pageid' => 'payment/stripe_webhook',
  'querystring' => 'params='.$params.'&opts='.$opts,
  'link_text' => 'Test Webhook',
  'target' => '_blank',
  'encrypt' => 'encrypt',
  'tooltip' => 'You can use this link to simulate a webhook callback');
$anchor = $this->site->dollar_function('link',$link);

By encrypting URLs, you can guarantee that a URL was generating from your site. If the page is not designated as requiring encrypted URLs (as described in option 1) you should always check to make sure that the URL was, in fact, encrypted to prevent hackers from passing parameters to the page using unencrypted URLs. This can be done by calling a method of the site object as shown here:

$trustworthy = $this->site->get_trusted_url();

Redirecting to a Different Page

Occasionally you may have the need to redirect the user to a different page. For example, we do this on the default credit card payment form after a payment has been performed. This helps to prevent the user from inadvertently attempting a duplicate payment. Redirection can be performed by calling the site object's redirect method. Here we see the signature of this method.

function redirect($pageid, $msg='', $querystring='', $encrypt=null, $permanent=false)

The first parameter is the name of the page to which you want to redirect. You can pass an empty string if you want to redirected to the default (home) page. You can see that the second parameter is an optional message that will be shown on the page being redirected to. To use this feature, the page that you are launching needs to be associated with a layout that supports messages. The supplied message can be a text string as shown here:

// Go to the home page and show a text message
$this->site->redirect('home','Thank you for completing your order');

The message can also be taken from a message that has been defined using the messages model as we show in this example:

// Go to the home page and show a message from the message file
$msg['nbr'] = 1234;
$sub['listid'] = 'order'; // Message directory (spec name)
$msg['text'] = 'Thank you for your payment of :1:'; // Fallback message if not found
$msg['subs'] = '$100'; // Make this an array if there are multiple substitutions
$msg['location'] = __LINE__.' '.__CLASS__;
$this->site->redirect('home',$msg);

You can also use the third parameter to pass information to the target page via the querystring. In this example we also request the url to be encrypted.

// Pass an order number and encrypt the url
$this->site->redirect('order_info','Here are your order detaiils','order-number='.$order,true);

By default, all redirects will be designated as temporary redirects. You can change this to a permanent redirect by setting the last parameter to true.

Other Common Values

Here we show the code used to implement the $site function. This shows how many common values can be retrieved by fetching various methods of the site object. 

switch ($this->property) {
  case 'url':
    return $this->site->get_site_url();
  case 'framework_url':
    return $this->site->get_framework_url();
  case 'inherit_url':
    return $this->site->get_industry_url();
  case 'tld':
    return $this->site->get_top_level_domain();
  case 'default_page':
    return $this->site->settings->get_default_page();
  case 'current_page':
    return $this->site->get_current_pageid();
  case 'company_name':
    return $this->site->settings->get_company_name();
  case 'default_date_format':
    return $this->site->settings->get_default_date_format();
  case 'default_time_format':
    return $this->site->settings->get_default_time_format();
  case 'default_language':
    return $this->site->settings->get_default_language();
  case 'current_language':
    return $this->site->get_language('en'); // Default to en if not set
  case 'current_locale':
    return $this->site->get_locale();
  case 'default_schema':
    return $this->site->settings->get_default_schema();
  case 'protocol':
    return $this->site->settings->get_url_protocol();   
  case 'site_directory':
    return $this->site->get_site_directory();   
  default:
     $this->set_error_message('Invalid site property "'.$this->property.'"');
     return false;
}

Building E-Commerce Applications

Genhelm includes a number of components to help you build an e-commerce site. Follow this link to learn how to build an e-commerce site. This help series also provides code samples that will help you learn how to program with GenHelm even if you are not planning to build an e-commerce site.

Help Index

GenHelm Architecture GenHelm Architecure.
Why GenHelm? Summary of the GenHelm methodology.
Getting Started Learn how to start a new website.
Naming Conventions Naming web pages and other items.
Common Web Page Fields Descriptions of fields that are common to all page models.
Layouts Defining codeframes and layouts.
Styles and Scripts Descriptions of fields that are common to all page models.
Meta Tags How to configure meta tags for your site and specific pages.
Dollar Functions General information about dollar functions.
Navigation Tips Navigation Tips and Techniques
Favourite Icons How to configure favorite icons for your site.
Blog Maintenance Blog and post administration.
Direct Command Help Help for the direct command field.
Programming with GenHelm Writing programs that interact with the GenHelm framework.