Skip to content

Instantly share code, notes, and snippets.

@stut
Last active May 31, 2016 19:16
Show Gist options
  • Save stut/6242cd7c43ba9112b7be3e9d73ebc9e6 to your computer and use it in GitHub Desktop.
Save stut/6242cd7c43ba9112b7be3e9d73ebc9e6 to your computer and use it in GitHub Desktop.
PHP models
<?php
// Table: An ActiveRecord-style DB abstraction class
// Copyright (c) 2005-2006 Stuart Dallas
// Released into the public domain with absolutely no warranty, explicit, implied or otherwise
// Use at your own risk!!
// If you do use this code please drop an email to code at stut dot net and let me know why ;)
// Patches, comments, suggestions, questions, etc are welcomed
// The MODEL_CACHE_DIR is where the table definitions get cached. This can be anywhere you like, defaults to cache/
// in the same location as this file. Obviously the web user (usually nobody or www) needs to have write access to
// this directory.
define('MODEL_CACHE_DIR', dirname(__FILE__).'/cache/');
// Make sure the directory exists, or try to create it if not
if (!file_exists(MODEL_CACHE_DIR))
mkdir(MODEL_CACHE_DIR) or die('Failed to create model cache directory on line '.__LINE__.' of '.__FILE__);
class Table
{
protected $table = ''; // What table does this map to
protected $idfield = 'id'; // If there is an ID field, but it's not called id, override this
protected $readonly = false; // Access to this table is read only
protected $data = false; // Internal array of row data
protected $dirty = false; // Is this row dirty?
protected $disregarddirty = false; // Do we care that it's dirty?
protected $isnew = true; // Is it a new row?
protected $customfieldtypes = array(); // Overrides of the default field types (for automatic form creation)
protected static $fields = array(); // Per-request cache of the table definitions
// Init is called by the constructors of subclasses
// It is responsible for loading the table definition
public function Init($classname, $conditions = '', $order = '')
{
// Have we been called statically or dynamically?
// Either way we need an instance to work with
if (isset($this))
$obj = &amp;$this;
else
$obj = new $classname();
// The table variable should be set by the subclass, if not use the class name
if ($obj->table == '')
$obj->table = strtolower($classname);
// Only load the field defs if this model has not been used yet in this request
if (!isset(self::$fields[$obj->table]))
{
// Build the base filename of the cache files for this table
$cachefilename = MODEL_CACHE_DIR.$obj->table;
// Does the field definition cache file exist? If not we need to build it
if (!file_exists($cachefilename.'.fields'))
{
// Initialise the cache (stored statically in the Table class)
self::$fields[$obj->table]['fields'] = array();
self::$fields[$obj->table]['autonumber'] = array();
self::$fields[$obj->table]['primarykey'] = array();
// Get the field definitions
$query = mysql_query('show columns from '.$obj->table);
while ($row = mysql_fetch_assoc($query))
{
// We store auto_number fields separately for use when creating new rows
if (strpos($row['Extra'], 'auto_increment') !== false)
self::$fields[$obj->table]['autonumber'][] = $row['Field'];
else
self::$fields[$obj->table]['fields'][$row['Field']] = $row;
}
// Get the primary key fields
$query = mysql_query('show indexes from '.$obj->table);
while ($row = mysql_fetch_assoc($query))
{
if ($row['Key_name'] == 'PRIMARY')
self::$fields[$obj->table]['primarykey'][] = $row['Column_name'];
}
// Now save the definition in files in the model cache directory
file_put_contents($cachefilename.'.fields', serialize(self::$fields[$obj->table]['fields']));
file_put_contents($cachefilename.'.autonumber', serialize(self::$fields[$obj->table]['autonumber']));
file_put_contents($cachefilename.'.primarykey', serialize(self::$fields[$obj->table]['primarykey']));
}
else
{
// The cache files exist, load them
self::$fields[$obj->table]['fields'] = unserialize(file_get_contents($cachefilename.'.fields'));
self::$fields[$obj->table]['autonumber'] = unserialize(file_get_contents($cachefilename.'.autonumber'));
self::$fields[$obj->table]['primarykey'] = unserialize(file_get_contents($cachefilename.'.primarykey'));
}
}
// Did we get given conditions?
if (strlen($conditions) > 0)
{
// If it's numeric, use it as an ID and build a where clause. This relies upon the idfield member variable that
// should be overridden in subclasses if the ID field is not named 'id'
if (is_numeric($conditions))
{
$conditions = $this->idfield.' = '.Table::Escape($conditions);
}
// Try to find a row matching the conditions
if ($obj->FindFirst($conditions, $order) === false)
{
// Couldn't find one, reset this object so it's an unsaved new row
$obj->Clear();
}
}
else
{
// No conditions, it's an unsaved new row
if($obj->data === false) $obj->Clear();
}
}
// The destructor does nothing more than a sanity check to see if this row is dirty (i.e. contains unsaved data), in which
// case it raises an error. The disregarddirty flag can be used to disable this where destroying a dirty object is expected
public function __destruct()
{
if ($this->dirty &amp;&amp; !$this->disregarddirty)
trigger_error(get_class($this).'.__destruct: Dirty object being released');
}
// Ahh, magic functions!!
// This one allows you to use OO syntax to access fields, e.g. $obj->id will get you the id field
public function __get($field)
{
return (isset($this->data[$field]) ? $this->data[$field] : '');
}
// The other half of __get is __set. This method is called if you do something like $obj->id = 100
public function __set($field, $value)
{
// If we're read only then changing the data is not allowed
if ($this->readonly)
{
trigger_error(get_class($this).'.__set: Attempt to modify a readonly object');
}
// If the row is not new then the primary key is read only
elseif (!$this->isnew and in_array($field, self::$fields[$this->table]['primarykey']))
{
trigger_error(get_class($this).'.__set: Attempt to set primary key field "'.$field.'"');
}
// Now we check to make sure the attribute being set actually exists (i.e. is in the table fields or is already in the
// data array)
elseif (isset(self::$fields[$this->table]['fields'][$field]) or isset($this->data[$field]))
{
if (!isset($this->data[$field]) or $this->data[$field] != $value)
$this->dirty = true;
$this->data[$field] = $value;
}
// If we get here then we don't know anything about the field being set - that's an error that is!
else
{
trigger_error(get_class($this).'.__set: Unknown field "'.$field.'"');
}
}
// LoadFromArray does what it says on the tin. It fills in the data for this record from an array
private function LoadFromArray($arr)
{
$this->data = array();
// Only allow fields we know about, ignore anything else
foreach (array_keys(self::$fields[$this->table]['fields']) as $field)
if (isset($arr[$field]))
$this->data[$field] = $arr[$field];
foreach (self::$fields[$this->table]['autonumber'] as $field)
if (isset($arr[$field]))
$this->data[$field] = $arr[$field];
// At first glance these might seem wrong, but this method is used when reading > 1 row (see the FindAll method) and since it's
// a private method we know the data source will always be the database, so it's not new and it's not dirty
$this->dirty = false;
$this->isnew = false;
// Call table-specific translation
$this->AfterLoad();
}
// Call this function to disable the error generated when a dirty object is destructed
public function DisregardDirty()
{
$this->disregarddirty = true;
}
// Clear the object of data.
protected function Clear()
{
$this->data = array();
}
// Stubs for table-specific stuff
protected function AfterLoad() { }
protected function BeforeSave() { }
protected function AfterDelete($result) { }
protected function BeforeDelete() { }
// GetFieldType first looks in the customfieldtypes array to see if the subclass has overridden it before returning the
// field type from the table definition. If we don't know anything about the field, assume it's a string
public function GetFieldType($field)
{
if (isset($this->customfieldtypes[$field]))
return $this->customfieldtypes[$field];
if (isset(self::$fields[$this->table]['fields'][$field]['Type']))
return self::$fields[$this->table]['fields'][$field]['Type'];
return 'string';
}
// GetValueFromDB will re-fetch a single value from the DB - useful for flags, locks, etc
public function GetValueFromDB($field)
{
$retval = $this->GetValuesFromDB(array($field));
return $retval[$field];
}
// GetValuesFromDB will get a given set of fields from this row in the table
// Note that it does not update the internal data
public function GetValuesFromDB($fieldlist = array())
{
$retval = false;
if (!$this->isnew and count($fieldlist) > 0)
{
$sql = 'select '.implode(',', $fieldlist).' from '.$this->table.' where '.$this->PrimaryKeyWhere();
$query = mysql_query($sql);
if ($query !== false and mysql_num_rows($query) == 1)
{
$row = mysql_fetch_assoc($query);
$retval = array();
foreach ($row as $key => $val)
$retval[$key] = $val;
}
}
return $retval;
}
// GetFieldList will produce an array containing the table definition
public function GetFieldList($pkey = true, $normal = true)
{
$retval = array();
if ($pkey) $retval['primarykey'] = self::$fields[$this->table]['primarykey'];
if ($normal) $retval['normal'] = self::$fields[$this->table]['fields'];
return $retval;
}
// Reload will update this object from the table
// Reloading a new row (duh!!) or reloading a dirty object will raise errors
public function Reload($ignoredirty = false)
{
if ($this->isnew)
{
trigger_error(get_class($this).'.Reload: Attempted to reload a new object');
}
else
{
if (!$ignoredirty and !$this->disregarddirty and $this->dirty)
trigger_error(get_class($this).'.Reload: Reloading a dirty object - changes lost');
// Reload from DB
$this->FindFirst($this->PrimaryKeyWhere());
}
}
// CreateInsertSQL is a helper function used to build insert statements
private function CreateInsertSQL($setfields)
{
return 'insert into '.$this->table.' set '.implode(', ', $setfields);
}
// Save this row
public function Save()
{
$retval = false;
// Can't save a read only row
if ($this->readonly)
{
trigger_error(get_class($this).'.Save: Attempt to save a readonly object');
}
// Are we dirty?
elseif ($this->dirty)
{
// Call table-specific translation
$this->BeforeSave();
// Get the fields
$setfields = array();
foreach (array_keys(self::$fields[$this->table]['fields']) as $field)
{
if (isset($this->data[$field]))
$setfields[$field] = $field.' = '.self::Escape($this->data[$field]);
}
// Is this a new row?
if ($this->isnew)
{
// Insert a new row
$sql = $this->CreateInsertSQL($setfields);
}
else
{
// Update existing row
$sql = 'update '.$this->table.' set '.implode(', ', $setfields).' where '.$this->PrimaryKeyWhere();
}
$result = mysql_query($sql);
if ($result === false)
{
// Something bad happened!
trigger_error(get_class($this).'.Save: Failed to save object - '.mysql_error());
$retval = false;
}
else
{
// Saved successfully, we're now clean
$this->dirty = false;
$retval = true;
// If this was a new row we need to grab the auto_number'd id
if ($this->isnew)
{
$this->data[$this->idfield] = mysql_insert_id();
// We're no longer a new row
$this->isnew = false;
}
}
// Call table-specific stuff
$this->AfterLoad();
}
else
{
// Not dirty, call it a success!
$retval = true;
}
return $retval;
}
// Delete will delete this row from the table
public function Delete()
{
$retval = false;
// Can't delete a new row, it doesn't actually exist yet!
if ($this->isnew)
{
trigger_error(get_class($this).'.Delete: Attempted to delete a new object');
}
else
{
// Call table-specific stuff
$this->BeforeDelete();
// Do the delete
$sql = 'delete from '.$this->table.' where '.$this->PrimaryKeyWhere();
$retval = mysql_query($sql);
// Call table-specific stuff
$this->AfterDelete($retval);
}
return $retval;
}
// PrimaryKeyWhere builds a where clause from the fields in the primary key
// This effectively produces a where clause that will retrieve the row this object is representing
public function PrimaryKeyWhere()
{
$wherefields = array();
foreach (self::$fields[$this->table]['primarykey'] as $field)
$wherefields[] = $field.' = '.self::Escape($this->data[$field]);
return '('.implode(' and ', $wherefields).')';
}
// FindAll takes a set of conditions in the form of a where clause, a limit clause and an order specification,
// builds a query from them and executes it. The rows returned are used to create an array of objects which is
// then returned
// Note that this method can only be called on subclasses - it cannot be called directly on the Table class
public function FindAll($conditions = '', $limit = '', $order = '')
{
$sql = 'select * from '.$this->table;
if (strlen($conditions) > 0)
$sql .= ' where '.$conditions;
if (strlen($order) > 0)
$sql .= ' order by '.$order;
if (strlen($limit) > 0)
$sql .= ' limit '.$limit;
$query = mysql_query($sql);
// Errors will currently cause an error to be raised. This may not be ideal for your application, you may need
// to change how these are handled
if (!$query)
trigger_error('MySQL error in '.get_class($this).'::FindAll: '.mysql_error());
// If the query got no rows return an empty array
if (mysql_num_rows($query) == 0)
return array();
$classname = get_class($this);
$retval = array();
while ($row = mysql_fetch_assoc($query))
{
$obj = new $classname();
$obj->LoadFromArray($row);
$retval[] = $obj;
}
return $retval;
}
// FindFirst does the same as FindAll but only gets a single row and returns a single object
public function FindFirst($conditions = '', $order = '')
{
$sql = 'select * from '.$this->table;
if (strlen($conditions) > 0)
$sql .= ' where '.$conditions;
if (strlen($order) > 0)
$sql .= ' order by '.$order;
$sql .= ' limit 1';
$query = mysql_query($sql);
// Errors will currently cause an error to be raised. This may not be ideal for your application, you may need
// to change how these are handled
if (!$query)
trigger_error('MySQL error in '.get_class($this).'::FindFirst: '.mysql_error());
// If the query got no rows return false rather than a new empty object
if (mysql_num_rows($query) == 0)
return false;
$row = mysql_fetch_assoc($query);
$this->LoadFromArray($row);
return $this;
}
// Paged is similar to FindAll but it will get a certain range of rows given a page number and the number of rows
// on each page. Returns an array where [0] is an array of rows, [1] is the current page number (in case it was
// adjusted) and [2] is the total number of pages available
public function Paged($page = 1, $perpage = 10, $conditions = '', $order = '')
{
// Get the total count
$sql = 'select count(1) from '.$this->table;
if (strlen($conditions) > 0)
$sql .= ' where '.$conditions;
$query = mysql_query($sql);
// Query failed, raise an error
if (!$query)
trigger_error('MySQL error in '.get_class($this).'::Paged: '.mysql_error());
// Because it's a count query this should never happen, but handle nicely just in case
if (mysql_num_rows($query) == 0)
return array(array(), 1, 1);
$row = mysql_fetch_array($query);
$rowcount = $row[0];
// Return if there are no matching rows
if ($rowcount == 0)
return array(array(), 1, 1);
// Make sure the requested page number is in range
$totalpages = ceil($rowcount / $perpage);
if ($page > $totalpages) $page = $totalpages;
if ($page < 1) $page = 1;
// Build the query
$sql = 'select * from '.$this->table;
if (strlen($conditions) > 0)
$sql .= ' where '.$conditions;
if (strlen($order) > 0)
$sql .= ' order by '.$order;
$sql .= ' limit '.(($page-1) * $perpage).','.$perpage;
$query = mysql_query($sql);
// Query failed, raise an error
if (!$query)
trigger_error('MySQL error in '.get_class($this).'::Paged: '.mysql_error());
// No rows returned, this shouldn't be possible but handle nicely just in case
if (mysql_num_rows($query) == 0)
return array(array(), 1, 1);
// Create the object array
$classname = get_class($this);
$retval = array();
while ($row = mysql_fetch_assoc($query))
{
$obj = new $classname();
$obj->LoadFromArray($row);
$retval[] = $obj;
}
// Return the rows, the page number and the number of pages
return array($retval, $page, $totalpages);
}
// Count returns the number of rows that match a condition
public function Count($conditions)
{
$sql = 'select count(*) as count from '.$this->table;
if (strlen($conditions) > 0)
$sql .= ' where '.$conditions;
$query = mysql_query($sql);
if (!$query or mysql_num_rows($query) == 0)
return false;
$row = mysql_fetch_assoc($query);
return $row['count'];
}
// Is this row new?
public function IsNew()
{
return $this->isnew;
}
//////////////////////////////
// Static utility functions //
//////////////////////////////
// Escape should be used to escape all values used in conditions
static public function Escape($var)
{
return '"'.mysql_real_escape_string($var).'"';
}
// MakeLike returns a like query for a given var and val
static public function MakeLike($var, $val)
{
return '(`'.$var.'` like "%'.mysql_real_escape_string($val).'%")';
}
// MakeLikes takes an array of vars and a single val and returns a set of likes combined by op
static public function MakeLikes($vars, $val, $op = 'or')
{
$likes = array();
foreach ($vars as $var)
$likes[] = self::MakeLike($var, $val);
return '('.implode(' '.$op.' ', $likes).')';
}
// Lock and Unlock wrap the table locking system
static protected function Lock($tables = false)
{
if ($tables === false)
trigger_error("Table::Lock called without a table to lock");
if (!is_array($tables))
$tables = array($tables);
return mysql_query('lock tables `'.implode('`,`', $tables).'`');
}
static protected function Unlock()
{
return mysql_query('unlock tables');
}
// GetSingleValue will return the first value of the first row returned by the provided SQL
// Caller should make sure it's only getting one field and one row
static public function &amp; GetSingleValue($sql)
{
$query = mysql_query($sql);
if ($query === false)
{
trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
exit;
}
if (mysql_num_rows($query) == 0)
return false;
$retval = mysql_fetch_array($query);
mysql_free_result($query);
return $retval[0];
}
// GetSingle will return an associative array containing the first row returned by the provided SQL
// Caller should make sure it's only getting one row
static public function &amp; GetSingle($sql)
{
$query = mysql_query($sql);
if ($query === false)
{
trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
exit;
}
if (mysql_num_rows($query) == 0)
return array();
$retval = mysql_fetch_assoc($query);
mysql_free_result($query);
return $retval;
}
// GetMultiple will return an array of associative arrays containing every row returned by the provided SQL
// Be careful not to get too many rows with this method - it loads them all into memory!!
static public function &amp; GetMultiple($sql)
{
$query = mysql_query($sql);
if ($query === false)
{
trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
exit;
}
$retval = array();
if (mysql_num_rows($query) > 0)
{
while ($row = mysql_fetch_assoc($query))
$retval[] = $row;
}
mysql_free_result($query);
return $retval;
}
// GetSingleColumn will return an array containing the first field of each row returned by the provided SQL
// Be careful not to get too many rows with this method - it loads them all into memory
static public function &amp; GetSingleColumn($sql)
{
$query = mysql_query($sql);
if ($query === false)
{
trigger_error('Query failed: '.mysql_error(), E_USER_ERROR);
exit;
}
$retval = array();
if (mysql_num_rows($query) > 0)
{
while ($row = mysql_fetch_array($query))
$retval[] = $row[0];
}
mysql_free_result($query);
return $retval;
}
// Modify is intended to execute a SQL statement that will make a change (insert, update, alter, etc)
static public function Modify($sql)
{
$query = mysql_query($sql);
if ($query === false)
return false;
return true;
}
// ModifyWithAutonumber is the same as Modify but returns the autonumber ID
static public function ModifyWithAutonumber($sql)
{
$query = mysql_query($sql);
if ($query === false)
return false;
return mysql_insert_id();
}
}
<?php
// Model: Account
// Pull in the table definition
require_once('table.class.php');
class Account extends Table
{
// The table we're mapping to is called accounts
protected $table = 'accounts';
// We have a number of fields we want to treat differently
// Note that these definitions don't affect how the data is treated, it just changes what is reported
// by the GetFieldType method in the Table class - this was added to allow automatic generation of
// forms
// An array indicates something akin to an enumeration where the actual value used can be defined
// differently (look at ACM in AccountType)
protected $customfieldtypes = array('Status' => array('Created', 'Active', 'Expired', 'Deleted'),
'Created' => 'date',
'Expires' => 'date',
'AccountType' => array('Full', 'Demo', 'ACM' => 'acm'),
);
// AfterLoad is called whenever the Table class completes loading of the data
// It can be used to...
protected function AfterLoad()
{
// ...enforce consistency...
if (isset($this->data['AccountType']))
{
// If ACM, the accounttype should be lowercase
if (strtolower($this->data['AccountType']) == 'acm')
$this->data['AccountType'] = 'acm';
}
// ...present data to consumers in a different format to that which is stored in the table...
if (isset($this->data['Created']) and $this->data['Created'] > 0)
$this->data['Created'] = date('Y-m-d', $this->data['Created']);
if (isset($this->data['Expires']) and $this->data['Expires'] > 0)
$this->data['Expires'] = date('Y-m-d', $this->data['Expires']);
// ...including complex types...
if (isset($this->data['OtherInfo']))
$this->data['OtherInfo'] = unserialize($this->data['OtherInfo']);
// ...and it can be used to create pseudo fields that do not exist in the table but may exist in other tables
$this->data['projects'] = array();
}
// BeforeSave is called by the Table class right before it saves the data back to the table
// It has the reverse purpose of AfterLoad, so you can...
protected function BeforeSave()
{
// ...store complex types...
if (isset($this->data['OtherInfo']) and is_array($this->data['OtherInfo']))
$this->data['OtherInfo'] = serialize($this->data['OtherInfo']);
// ...convert data to a format suitable for storage in a table...
if (isset($this->data['Expires']) and strlen($this->data['Expires']) > 0 and $this->data['Expires'] != 0)
$this->data['Expires'] = strtotime($this->data['Expires']);
// ...and forcing default values in new rows
if ($this->isnew)
{
$this->data['Created'] = time();
if (!isset($this->data['Status'])) $this->data['Status'] = 'Created';
}
else
{
if (isset($this->data['Created']) and strlen($this->data['Created']) > 0 and $this->data['Created'] != 0)
$this->data['Created'] = strtotime($this->data['Created']);
}
// Note the absense of any reference to the projects variable
// This is fine since the Save method of the Table class uses its own internal list of fields to decide which parts of
// the data to save to the table
}
// In addition to overriding methods in the Table class, we can create methods that are specific to this particular table
// For example, this method will return true only if the AccountType is set to acm (Account Manager)
public function IsACM()
{
return (isset($this->data['AccountType']) and $this->data['AccountType'] == 'acm');
}
// Methods can also return related rows
// For example, GetACM will return an Account object representing this objects Account Manager
public function &amp; GetACM()
{
$retval = false;
if ($this->data['ACM'] > 0)
{
$retval = new Account('id = '.$this->data['ACM']);
}
return $retval;
}
// In true OO tradition, any method in this class should be related to an Account
// For example, IsExpired will return true if the account has expired...
public function IsExpired()
{
return (isset($this->data['Expires']) and $this->data['Expires'] != 0 and strtotime($this->data['Expires']) < time());
}
// ...MarkDeleted will set the status of the Account and save it back to the database...
public function MarkDeleted()
{
$this->Status = 'Deleted';
// Note that calling Save will cause BeforeSave to be called, the data will then be saved and finally AfterLoad will be called
// This means that the member variable named data will be reset, along with any custom fields put in by AfterLoad
return $this->Save();
}
// ...performing actions like resetting the account password...
public function ResetPassword($password = '')
{
// If no password was given, generate a random one
$pwd = $password;
if (strlen($pwd) == 0)
$pwd = generatePassword(); // Function source omitted since it's irrelevant
// Save it
$this->Password = $pwd;
if (!$this->Save())
return false;
if ($password == '')
{
// Password was random, email it to the address held in the account
if (strlen($this->data['EmailAddress']) > 0)
mail($this->data['EmailAddress'], 'Password Updated', 'Your new password is: '.$pwd, "From: Support <[email protected]>", '[email protected]');
}
return true;
}
// ...getting or counting other records...
public function ACM_GetAccounts($countonly = false)
{
if ($countonly)
{
return $this->Count('ACM = '.self::Escape($this->data['id']));
}
else
{
return $this->FindAll('ACM = '.self::Escape($this->data['id']), '', 'name asc');
}
}
// ...ensuring that deletions get propogated to dependent data...
public function Destroy()
{
$accounts = $this->ACM_GetAccounts();
foreach ($accounts as $account)
{
$result = $account->Destroy();
if ($result !== true)
return $result;
}
// Call destroy on all projects first - Project is another class derived from Table
$tmp = new Project();
$projects = $tmp->FindAll('accountid = '.self::Escape($this->data['id']));
foreach ($projects as $project)
{
$result = $project->Destroy();
if ($result !== true)
return $result;
}
// Then delete this account
$result = $this->Delete();
if ($result !== true)
return 'Failed to delete account '.$this->data['id'];
return true;
}
// ...other functions omitted for clarity
// You can also define static methods
// Account::Current() will get you an account object from the session (see the next method, Login)
static public function &amp; Current()
{
$retval = false;
if (isset($_SESSION['account']))
$retval = $_SESSION['account'];
return $retval;
}
// The Login method takes an email address and a password and tries to log the user in
// If login is successful it stores the Account object in the session so it can be retrieved by the Current method
static public function Login($email, $password)
{
$account = new Account();
if ($account->FindFirst('EmailAddress = '.Table::Escape($email).' and Password = '.Table::Escape($password)) !== false)
{
$_SESSION['account'] = &amp;$account;
return true;
}
return false;
}
// Static functions can also be used to return multiple rows
// This one will return an array of objects containing the Account Manager records
static public function &amp; GetACMs()
{
$account = new Account();
return $account->FindAll('AccountType = '.Table::Escape('acm'), '', 'name asc');
}
// The rest is exactly the same for every class that derives from Table - absolutely nothing needs changing from class to class,
// but it's vital that these exist - see the next bit of the article for an explanation
// The constructor takes conditions and order and passes them, along with the class name, to the Init method
public function __construct($conditions = '', $order = '') { $this->Init(__CLASS__, $conditions, $order); }
// The magic __wakeup method is called when an object is read from serialised data (e.g. the session). It also calls the Init
// method but just with the class name
public function __wakeup() { $this->Init(__CLASS__); }
}
<?php
class A
{
public function WhoAmI_1()
{
return __CLASS__;
}
public function WhoAmI_2()
{
return get_class();
}
public function WhoAmI_3()
{
return get_class($this);
}
}
class B extends A
{
}
print A::WhoAmI_1();
print B::WhoAmI_1();
print A::WhoAmI_2();
print B::WhoAmI_2();
<?php
$a = new A();
$b = new B();
print $a->WhoAmI_3();
print $b->WhoAmI_3();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment