Ok, so I’ll try to explain briefly how it works:
-
Front controller (index.php with some helper classes) gets the name of the action from url and fires specific method in a proper controller. List of actions assigned to controllers is kept in a db table. Generally, I have one controller per db table, that’s what my automated system for admin generates, of course I can add more controllers when I need.
-
An example of a controller with basic methods for adding, editing, listing, deleting, archiving and moving records in the payment_type table. I know they probably are too big as most of this logic should go to the model but they are generated automatically and I’m too lazy to restructure all of that. I have skipped the content of the last methods since that’s a lot of code. $portlet is an object for passing data to the templates. As you can see all basic CRUD is done without SQL:
<?php
// FlyControleer - I named my system 'Fly', hence the name :)
class PaymentTypeController extends FlyController {
/* form for adding record */
public function addPaymentTypeForm($request, $portlet = null) {
if (!$portlet) {
$portlet = new FlyPortlet;
$paymentType = new PaymentType;
} else {
$paymentType = $portlet->data['formData'];
}
$portlet->data['formAction'] = "index.php?a=addPaymentType";
$portlet->data['actionType'] = 'add';
$portlet->data['formData'] = $paymentType;
$portlet->setTemplate("admin/payment_type-form.tpl");
return $portlet;
}
/* adding record action */
public function addPaymentType($request, $portlet = null) {
// this gets all data from posted from form into a new paymentType object.
// true means that all fields should go through trim() function
$paymentType = new PaymentType(@$request['post']['payment_type'], true);
$validator = new Validator;
$validator->validatePaymentType($paymentType);
$validationErrors = $validator->getValidationErrors();
if ($validationErrors) {
// errors in form
$portlet = new FlyPortlet;
$portlet->data['formData'] = $paymentType;
$portlet->data['validationErrors'] = $validationErrors;
$this->setGlobalMessage("There are errors in the form!", "error");
return $this->addPaymentTypeForm($request, $portlet);
}
// validation ok, save form data
$paymentType->save();
$this->setGlobalMessage("Data have been added");
return new FlyAdminRedirect('listPaymentTypes');
}
/* form for editing record */
public function editPaymentTypeForm($request, $portlet = null) {
if (!$portlet) {
$portlet = new FlyPortlet;
if (!$paymentType = PaymentTypePeer::getByPK(@$request['get']['payment_type_id'])) {
throw new Exception("Requested record does not exist in database");
}
$portlet->data['formData'] = $paymentType;
} else {
$paymentType = $portlet->data['formData'];
}
$portlet->data['formAction'] = "index.php?a=editPaymentType";
$portlet->data['actionType'] = 'edit';
$portlet->setTemplate("admin/payment_type-form.tpl");
return $portlet;
}
/* editing record action */
public function editPaymentType($request, $portlet = null) {
$db = Reg::$db;
$paymentType = PaymentTypePeer::getByPK(@$request['post']['PK']);
if (!$paymentType) {
throw new Exception("Form error");
}
$paymentType->loadData($request['post']['payment_type'], true, array('for_poland', 'abroad'));
$validator = new Validator;
$validator->validatePaymentType($paymentType);
$validationErrors = $validator->getValidationErrors();
if ($validationErrors) {
// errors in form
$portlet = new FlyPortlet;
$portlet->data['formData'] = $paymentType;
$portlet->data['validationErrors'] = $validationErrors;
$this->setGlobalMessage("There are errors in the form!", "error");
return $this->editPaymentTypeForm($request, $portlet);
}
// validation ok, save form data
$paymentType->save();
if ($paymentType->hasChanged()) {
$this->setGlobalMessage("Data have been saved");
} else {
$this->setGlobalMessage("Data have not been saved, probably you have made no changes", "alert");
}
return new FlyAdminRedirect('listPaymentTypes');
}
/* record listing */
public function listPaymentTypes($request, $portlet = null) {
}
/* delete record */
public function deletePaymentType($request, $portlet = null) {
}
/* move to archive */
public function movePaymentTypeToArchive($request, $portlet = null) {
}
/* restore from archive */
public function restorePaymentTypeFromArchive($request, $portlet = null) {
}
/* move selected items */
function movePaymentType($request) {
}
}
?>
- Model objects. Similar structure to Propel - there are base classes generated automatically with basic methods for accessing row objects and static base peer classes with methods not concerned with any specific data object. Both of these are extended by classes where I add my own stuff.
Example of BasePaymentType class:
<?php
/* Do not edit this class! Use extended class for your stuff. */
class BasePaymentType extends DbRecord {
protected $_primaryKeyName = 'payment_type_id';
protected $_tableName = 'payment_type';
protected $_className = 'PaymentType';
protected $_peerName = 'PaymentTypePeer';
protected $_PKQuoteStyle = 's';
protected $_hasChanged = false;
public $_originalData;
public $payment_type_id = '';
public $name_pl = '';
public $name_en = '';
public $name2_pl = '';
public $name2_en = '';
public $desc_pl = null;
public $desc_en = null;
public $for_poland = '';
public $abroad = '';
public $commission = '0.00';
public $constant_commission = '0.00';
public $payment_fee_usd = '0.00';
public $payment_fee_info_pl = null;
public $payment_fee_info_en = null;
public $payment_fee_name_pl = null;
public $payment_fee_name_en = null;
public $item_status = '1';
public $item_pos = '';
public function __construct($data = null, $doTrim = false) {
parent::__construct($data, $doTrim);
}
public function getPrimaryKey() {
return $this->payment_type_id;
}
public function getPrimaryKeyName() {
return 'payment_type_id';
}
public function getDbTableName() {
return 'payment_type';
}
public function copy() {
$copy = clone $this;
$pkName = $this->_primaryKeyName;
$copy->$pkName = null;
$copy->item_pos = null;
$copy->_originalData = null;
return $copy;
}
public function getPaymentTypeId() {
return $this->payment_type_id;
}
public function getNamePl() {
return $this->name_pl;
}
public function getNameEn() {
return $this->name_en;
}
public function getName2Pl() {
return $this->name2_pl;
}
public function getName2En() {
return $this->name2_en;
}
public function getDescPl() {
return $this->desc_pl;
}
public function getDescEn() {
return $this->desc_en;
}
public function getForPoland() {
return $this->for_poland;
}
public function getAbroad() {
return $this->abroad;
}
public function getCommission() {
return $this->commission;
}
public function getConstantCommission() {
return $this->constant_commission;
}
public function getPaymentFeeUsd() {
return $this->payment_fee_usd;
}
public function getPaymentFeeInfoPl() {
return $this->payment_fee_info_pl;
}
public function getPaymentFeeInfoEn() {
return $this->payment_fee_info_en;
}
public function getPaymentFeeNamePl() {
return $this->payment_fee_name_pl;
}
public function getPaymentFeeNameEn() {
return $this->payment_fee_name_en;
}
public function getItemStatus() {
return $this->item_status;
}
public function getItemPos() {
return $this->item_pos;
}
}
?>
And the BasePaymentTypePeer class. The SHORT_FIELDS contstant contains all table fields which are not TEXT or BLOB, occasionally I use this for performance reasons where fetching long column data is unnecessary, but that is rare and somewhat ugly. ALL_FIELDS_FOR_JOIN is a list of fields that I can use for join queries when I want data from related tables to be put in appropriate objects as well.
<?php
/* Do not edit this class! Use extended class for your stuff. */
class BasePaymentTypePeer {
public static $cache = array();
public static $cacheFast = array();
const SHORT_FIELDS = 'payment_type.`payment_type_id`, payment_type.`name_pl`, payment_type.`name_en`, payment_type.`name2_pl`, payment_type.`name2_en`, payment_type.`for_poland`, payment_type.`abroad`, payment_type.`commission`, payment_type.`constant_commission`, payment_type.`payment_fee_usd`, payment_type.`payment_fee_name_pl`, payment_type.`payment_fee_name_en`, payment_type.`item_status`, payment_type.`item_pos`';
const ALL_FIELDS_FOR_JOIN = 'payment_type.`payment_type_id` AS `payment_type.payment_type_id`, payment_type.`name_pl` AS `payment_type.name_pl`, payment_type.`name_en` AS `payment_type.name_en`, payment_type.`name2_pl` AS `payment_type.name2_pl`, payment_type.`name2_en` AS `payment_type.name2_en`, payment_type.`desc_pl` AS `payment_type.desc_pl`, payment_type.`desc_en` AS `payment_type.desc_en`, payment_type.`for_poland` AS `payment_type.for_poland`, payment_type.`abroad` AS `payment_type.abroad`, payment_type.`commission` AS `payment_type.commission`, payment_type.`constant_commission` AS `payment_type.constant_commission`, payment_type.`payment_fee_usd` AS `payment_type.payment_fee_usd`, payment_type.`payment_fee_info_pl` AS `payment_type.payment_fee_info_pl`, payment_type.`payment_fee_info_en` AS `payment_type.payment_fee_info_en`, payment_type.`payment_fee_name_pl` AS `payment_type.payment_fee_name_pl`, payment_type.`payment_fee_name_en` AS `payment_type.payment_fee_name_en`, payment_type.`item_status` AS `payment_type.item_status`, payment_type.`item_pos` AS `payment_type.item_pos`';
const SHORT_FIELDS_FOR_JOIN = 'payment_type.`payment_type_id` AS `payment_type.payment_type_id`, payment_type.`name_pl` AS `payment_type.name_pl`, payment_type.`name_en` AS `payment_type.name_en`, payment_type.`name2_pl` AS `payment_type.name2_pl`, payment_type.`name2_en` AS `payment_type.name2_en`, payment_type.`for_poland` AS `payment_type.for_poland`, payment_type.`abroad` AS `payment_type.abroad`, payment_type.`commission` AS `payment_type.commission`, payment_type.`constant_commission` AS `payment_type.constant_commission`, payment_type.`payment_fee_usd` AS `payment_type.payment_fee_usd`, payment_type.`payment_fee_name_pl` AS `payment_type.payment_fee_name_pl`, payment_type.`payment_fee_name_en` AS `payment_type.payment_fee_name_en`, payment_type.`item_status` AS `payment_type.item_status`, payment_type.`item_pos` AS `payment_type.item_pos`';
/* Mapping for each field: array($quoteStyle, $isNull) */
public function getFieldMap() {
return array(
'payment_type_id' => array('s', false),
'name_pl' => array('s', false),
'name_en' => array('s', false),
'name2_pl' => array('s', false),
'name2_en' => array('s', false),
'desc_pl' => array('s', true),
'desc_en' => array('s', true),
'for_poland' => array('i', false),
'abroad' => array('i', false),
'commission' => array('d', false),
'constant_commission' => array('d', false),
'payment_fee_usd' => array('d', false),
'payment_fee_info_pl' => array('s', true),
'payment_fee_info_en' => array('s', true),
'payment_fee_name_pl' => array('s', true),
'payment_fee_name_en' => array('s', true),
'item_status' => array('i', false),
'item_pos' => array('i', false),
);
}
public function getByPK($PK, $use_cache = true) {
if (array_key_exists($PK, PaymentTypePeer::$cache) && $use_cache) {
return PaymentTypePeer::$cache[$PK];
}
return PaymentTypePeer::$cache[$PK] = Reg::$db->fetch_one_row("SELECT * FROM `payment_type` WHERE payment_type_id=" .Reg::$db->quote($PK, 's'), 'PaymentType');
}
public function getByPKFast($PK, $use_cache = true) {
if (array_key_exists($PK, PaymentTypePeer::$cacheFast) && $use_cache) {
return PaymentTypePeer::$cacheFast[$PK];
}
return PaymentTypePeer::$cacheFast[$PK] = Reg::$db->fetch_one_row("SELECT " .PaymentTypePeer::SHORT_FIELDS. " FROM `payment_type` WHERE payment_type_id=" .Reg::$db->quote($PK, 's'), 'PaymentType');
}
public function doSelect($sql, $joinedTables = null) {
if (strcasecmp(substr(ltrim($sql), 0, 6), "SELECT") == 0) {
return Reg::$db->fetch_all_rows_iterator($sql, "PaymentType", $joinedTables);
}
return Reg::$db->fetch_all_rows_iterator("SELECT * FROM `payment_type` ".$sql, "PaymentType");
}
public function doSelectFast($sql) {
return Reg::$db->fetch_all_rows_iterator("SELECT " .PaymentTypePeer::SHORT_FIELDS. " FROM `payment_type` ".$sql, "PaymentType");
}
public function doSelectOne($sql, $joinedTables = null) {
if (strcasecmp(substr(ltrim($sql), 0, 6), "SELECT") == 0) {
return Reg::$db->fetch_one_row($sql, "PaymentType", $joinedTables);
}
return Reg::$db->fetch_one_row("SELECT * FROM `payment_type` ".$sql, "PaymentType");
}
public function doSelectOneFast($sql) {
return Reg::$db->fetch_one_row("SELECT " .PaymentTypePeer::SHORT_FIELDS. " FROM `payment_type` ".$sql, "PaymentType");
}
public function reorderSort($whereSql) {
Reg::$db->query("SET @a = 0");
Reg::$db->query("UPDATE payment_type, (SELECT payment_type_id, @a:=@a+2 AS s FROM payment_type WHERE $whereSql ORDER BY item_pos ASC) dt
SET payment_type.item_pos = dt.s WHERE payment_type.payment_type_id = dt.payment_type_id;");
}
}
?>
And there are also db access classes like Db (extending mysqli), DbResult (extending MySQLi_ResultDbRecord), DbRecord and DbRecordIterator - they are used by the classes posted above. If you want I can post them as well, my code is not secret, it can just take too much space in case this is not something you are interested in.
I don’t consider this system an engineering marvel, it just works well and the automatic generation of classes, controllers and templates is something of much help. And it’s quite fast performance-wise, which results in some half-ugly solutions sometimes. But I like it a lot because it gets the job done. If there is anything more you want to know, I can post more code, no problem.